Rails Ajax call 404 error for the edit view - ruby-on-rails

I have a button on my new and edit views that sends a post request to my Letter controller through an Ajax call. If the Ajax call works perfectly in the new view, it throws a 404 error for my edit view.
Route:
post 'letters/ajax_send_test_botletter', to: 'letters#send_test_botletter', as: 'send_test_botletter'
The form is defined like this:
<%= form_for(letter, :html => {class: "directUpload", remote: true}) do |f| %>
The button triggering the Ajax call in the form:
<button class="cta3" id="send_test_letter">Send a test campaign to yourself</button>
Ajax call:
$('#send_test_letter').on('click', function(){
$('form').submit(function() {
var valuesToSubmit = $(this).serialize();
$.ajax({
type: "POST",
url: "/letters/ajax_send_test_botletter",
data: valuesToSubmit,
dataType: "JSON" // you want a difference between normal and ajax-calls, and json is standard
}).success(function(json){
if(json['value'] == "No Recipient") {
$('#send_test_letter').css('display', 'none');
$('#save_test_user').css('display', 'block');
} else {
console.log("Success")
$('#confirmation_test_sent').html('Test successfully sent. Check your Messenger.')
}
$('form').unbind('submit');
});
return false; // prevents normal behaviour
});
});
My send_test_botletter method
def send_test_botletter
#message_content = params[:letter]['messages_attributes']['0']['content']
#button_message = params[:letter]['messages_attributes']['0']['buttons_attributes']['0']['button_text'] if params[:letter]['messages_attributes']['0']['buttons_attributes']['0']['button_text'] != ''
#button_url = params[:letter]['messages_attributes']['0']['buttons_attributes']['0']['button_url'] if params[:letter]['messages_attributes']['0']['buttons_attributes']['0']['button_url'] != ''
#cards = params[:letter]['cards_attributes'] if params[:letter]['cards_attributes'].present? == true
#test_segment = Segment.where(core_bot_id: #core_bot_active.id, name: "test").first
#recipients = BotUser.where(core_bot_id: #core_bot_active.id, source: #test_segment.token)
if #recipients.exists?
send_message_onboarding if #message_content != '' and #button_message.present? == false
send_message_button_onboarding if #message_content != '' and #button_message.present? == true and #button_url.present? == true
send_card_onboarding if #cards
respond_to do |format|
format.json { render json: {"value" => "Success"}}
end
else
respond_to do |format|
format.json { render json: {"value" => "No Recipient"}}
end
end
end
I get the following error in the Chrome console for the edit view:
POST http://localhost:3000/letters/ajax_send_test_botletter 404 (Not
Found)
And in my Rails logs:
ActiveRecord::RecordNotFound (Couldn't find Letter with
'id'=ajax_send_test_botletter):
It seems it calls the Update method instead of the send_test_botletter method...
Any idea what's wrong here?

I found the trick. The problem was the PATCH method in the edit form.
I found a plugin in this discussion in order to modify the serialized data and change the method to "post":
$('#send_test_letter').on('click', function(){
$('form').submit(function() {
var valuesToSubmit = $(this).awesomeFormSerializer({
_method: 'post',
});
$.ajax({
type: "POST",
url: "/letters/ajax_send_test_botletter",
data: valuesToSubmit,
dataType: "JSON" // you want a difference between normal and ajax-calls, and json is standard
}).success(function(json){
if(json['value'] == "No Recipient") {
$('#send_test_letter').css('display', 'none');
$('#save_test_user').css('display', 'block');
} else {
console.log("Success")
$('#confirmation_test_sent').html('Test successfully sent. Check your Messenger.')
}
$('form').unbind('submit');
});
return false; // prevents normal behaviour
});
});
(function ( $ ) {
// Pass an object of key/vals to override
$.fn.awesomeFormSerializer = function(overrides) {
// Get the parameters as an array
var newParams = this.serializeArray();
for(var key in overrides) {
var newVal = overrides[key]
// Find and replace `content` if there
for (index = 0; index < newParams.length; ++index) {
if (newParams[index].name == key) {
newParams[index].value = newVal;
break;
}
}
// Add it if it wasn't there
if (index >= newParams.length) {
newParams.push({
name: key,
value: newVal
});
}
}
// Convert to URL-encoded string
return $.param(newParams);
}
}( jQuery ));

form_for(letter... generates a different url and method depending whether or not the instance is persisted, defaulting to create and post or update and patch as appropriate.
When you hit submit, it's trying to hit this endpoint, before your listener kicks in. And in doing so, breaks the remaining js.
However, you can also provide url and method options to form_for. Try providing a blank url option and the correct method (form_for letter, ..., url: '', method: :post).
Alternatively, you could stop the default behaviour / propagation on form submission:
$('form').submit(function(e) {
e.stopPropagation() // Or could simply be `preventDefault()`, depending on your use case
...
// your AJAX
}
Able to test out these approaches?
Update
Your method is actually nesting a submit listener within the click one. Try the following:
$('#send_test_letter').on('click', function(e){
e.stopPropagation()
var $form = $(this).closest('form')
var valuesToSubmit = $form.serialize();
$.ajax({
type: "POST",
url: "/letters/ajax_send_test_botletter",
data: valuesToSubmit,
dataType: "JSON" // you want a difference between normal and ajax-calls, and json is standard
}).success(function(json){
if(json['value'] == "No Recipient") {
$('#send_test_letter').css('display', 'none');
$('#save_test_user').css('display', 'block');
} else {
console.log("Success")
$('#confirmation_test_sent').html('Test successfully sent. Check your Messenger.')
}
return false; // prevents normal behaviour
});
});

Related

I have a 404 error in an Ajax call on Ruby on rails

I'm pretty new to ajax (and web development in general) and I'm trying to implement this code directly on my view:
<% content_for :after_js do %>
<script type="text/javascript">
//1st dropdown selection
const dropdownOne = document.querySelector('#order_credits_package_id');
//Listening to dropdown change
dropdownOne.addEventListener('change', (event) => {
const selectedPackage = event.target.value
console.log(selectedPackage)
// document.getElementById("order_amount_centavos").value = event.target.value
// Params for AJAX call
const url = "/dynamic_dropdown_method";
const data = {'selection': selectedPackage }
// Storage of AJAX call answer (NB: can be refactored to avoid 2 calls)
// the ['id'] must match the one defined in your controller
const level_1_selected = ajaxCall(url, data)['dropdown_1_selected'];
const level_2_array = ajaxCall(url, data)['dropdown_2_array'];
// Identification of 2nd dropdown
const dropdownTwo = document.querySelector('#order_amount_centavos');
// Delete and replace options in 2nd dropdown
removeOptions(dropdownTwo);
addOptions(level_2_array, dropdownTwo)
});
// AJAX call
function ajaxCall(url,data) {
var result = "";
console.log("call Ajax")
console.log(url)
console.log(data)
$.ajax({
url: url,
async: false,
dataType: 'json',
type: 'POST',
data: data,
success: function (response) {
console.log("ajax Call")
console.log(response)
result = response;
}
});
return result;
}
// Delete existing select options
function removeOptions(selectElement) {
console.log("fonction remove")
var i, L = selectElement.options.length - 1;
for(i = L; i >= 0; i--) {
selectElement.remove(i);
}
}
// Add select option
function addOptions(optionsArray, dropdown) {
optionsArray.forEach(function (item) {
const new_option = document.createElement('option');
new_option.value = item;
new_option.innerHTML = item;
dropdown.appendChild(new_option);
});
}
</script>
<% end %>
I then added this method to the controller related to this view:
def dynamic_dropdown_method
#selection = params[:credits_package_id]
#array_dropdown_2 = CreditsPackage.find(#selection).price_centavos
return render json: {level_1_selected: #selection, level_2_array: #array_dropdown_2}.to_json
end
And the following route:
post "/dynamic_dropdown_method", to: "orders#dynamic_dropdown_method", as: :dynamic_dropdown_method
But when I try to run my code, I get the following error message in the console:
POST http://localhost:3000/dynamic_dropdown_method 404 (Not Found)
Does anyone have any idea how to fix it?
Thank you very much!

How can I verify authenticity token when submitting form through ajax success?

I have a condition in which firstly the ajax request is triggered to fetch respective host url. In case the condition returned is true I want to submit the rails form. The form is submitted correctly but I get the error can't verify authenticity token.
$(document).ready(function() {
var url, email;
$("form#new_user").submit(function(e){
e.preventDefault();
email = document.getElementById("user_email").value;
$.ajax("/return_host", {
type: "GET",
data: {
email: email
},
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
},
success: function(data) {
if(data['goto'] == true){
host = data["host"];
url = host + "/users/sign_in";
$("form").attr("action", url);
$("form").trigger('submit.rails');
} else {
location.reload();
}
}
});
});
});
How can I send authenticity token when triggering rails form through ajax success?
Please check whether your form has a hidden input field named authenticity_token or not, for forms generated with rails helpers this field is included by default.
If not then this is what is causing error as rails expects a param called authenticity_token when you post a form.
You can add this on the fly in your success callback as shown below:
success: function(data) {
if(data['goto'] == true){
host = data["host"];
url = host + "/users/sign_in";
var form = $("form");
var authenticityToken = document.createElement("input");
authenticityToken.setAttribute("type", "hidden");
authenticityToken.setAttribute("name", "authenticity_token");
authenticityToken.setAttribute("value", $('meta[name="csrf-token"]').attr('content'));
form.append(authenticityToken);
form.attr("action", url);
form.trigger('submit.rails');
} else {
location.reload();
}
}
Hope it helps!
If you're on Rails 5.1+ I'd recommend using form_with helper to handle the ajax request [https://api.rubyonrails.org/v5.2.1/classes/ActionView/Helpers/FormHelper.html#method-i-form_with].
You'll still be able to hook into the success and error callbacks using code as I've shared below.
```
let form = document.getElementById('my_form')
form.addEventListener('ajax:error', (event) => {
let form = event.target
let [errors, status, xhr] = event.detail
// DO SOMETHING
})
form.addEventListener('ajax:success', (event) => {
let form = event.target
// DO SOMETHING
})
form.addEventListener('ajax:before', (event) => {
let form = event.target
// DO SOMETHING
})
```
This way rails will handle the authenticity token/csrf yet you'll still be able to use Ajax - with option data: { remote: true } to ensure no page reload.
Hope this helps.

How to respond to ajax call in Rails

I know there are many questions about that already on stackoverflow but none of them has been useful for me. Here is my ajax code:
function update_schedule($task){
var taskId = $task.data("taskId"),
startHour, endHour,
userId = $('#users').val();
if( !$task.hasClass('daily-task')){
startHour = getStartHour($task),
endHour = startHour + (+$task.data('time'));
}
console.log(startHour, endHour)
$.ajax({
url: '/heimdall/update_scheduled_task',
method: 'POST',
data: { "task_id": taskId, "start_hour": startHour, "end_hour": endHour, "user_id": userId },
success: function (){
console.log("SUCESS!!", data['head'])
},
error: function () {
console.log("FAILURE");
},
async: true
});
}
The controller code:
def update_scheduled_task
scheduled_task = ScheduledTask.find_or_create_by(task_id: params[:task_id])
scheduled_task.update_attributes(start_hour: params[:start_hour], end_hour: params[:end_hour], task_id: params[:task_id], user_id: params[:user_id])
end
I want to return the id of found or created task. I don't know how to send/receive this information. I've read about respond to but still I don't know how to use it in this case.
You may do render json: ... to return the required info to ajax in JSON format:
def update_scheduled_task
scheduled_task = ScheduledTask.find_or_create_by(task_id: params[:task_id])
scheduled_task.update_attributes(start_hour: params[:start_hour], end_hour: params[:end_hour], task_id: params[:task_id], user_id: params[:user_id])
render json: {scheduled_task_id: scheduled_task.id}
end
And in the ajax function's success, use it like:
success: function (data){
var data = JSON.parse(data);
console.log(data["scheduled_task_id"]);
},
First, you need to improve your controller code structure (You can rely on the default scaffold generator methods). Then, you must indicate that your method will respond to json format only, with the answer you want to return, something like this:
def update_scheduled_task
scheduled_task = ScheduledTask.find_or_create_by(task_id: params[:task_id])
if (scheduled_task && scheduled_task.update_attributes(start_hour: params[:start_hour], end_hour: params[:end_hour], task_id: params[:task_id], user_id: params[:user_id]))
render json: { scheduled_task_id: scheduled_task.id }
else
render json: { error: l18n.t("error.messages.request_error") }
end
end
Then, you must modify the success and failure response methods of the jquery ajax request. Here's an example of how it might look:
$.ajax({
url: "/heimdall/update_scheduled_task",
method: "post",
data: { task_id: taskId, start_hour: startHour, end_hour: endHour, user_id: userId },
success: function(result) {
if (result["scheduled_task_id"] != null) {
console.log("Schedule record => " + result["scheduled_task_id"])
} else {
console.log("Error: " + result["error"])
}
},
error: function() {
console.log("Ajax request error");
},
// async: true => By default JQuery set true to async param.
});
Do not forget that you need to add a rule to access this method in the file config/ruotes.rb, something like this:
post update_scheduled_task, :defaults => { :format => 'json' }
I hope this helped you, regards!
jQuery's success and error callbacks rely on the status code returned by the controller. Make sure that you return the right status code when you are unable to create/read/update the object. On success, simply render a JSON with the id property set. I beleive update_attributes returns true on success:
if scheduled_task && scheduled_task.update_attributes(...)
render json: { id: scheduled_task.id }
else
head :unprocessable_entity
end

Ruby on Rails textfield to route or specific webpage or to show action rather than list

The website i am building is in ruby on rails and it is about agriculture equipments. I have build a regular search page with input text field which when used list all items containing keyword
What i am planning to do is to have a textfield with bootstrap autocomplete feature. The text field will show options depending on input and when a single option is selected, i need it route to the particular items detail page i.e. show page rather than listing the results.
I need help with how to route directly to an items show page using the textfield value.
How can i do that?
Further to the comments, it sounds like you'll be wanting to create some sort of livesearch functionality, which basically uses ajax to send requests on keyup to your backend (PHP, Rails, etc)
We've done something like this here (search at the top):
--
The way to make this work is 3 fold:
Specific ajax route
Javascript to handle keyup
Controller action to send response
I understand this is not exactly what you want, but hopefully it will point you in the right direction
Routes
#config/routes.rb
resources :controller do
collection do
get "search(/:query)" #-> domain.com/controler/search/your_query
end
end
Controller
#app/controllers/your_controller.rb
class YourController < ApplicationController
respond_to :json, :js, :html
def search
#response = Model.where value: params[:query]
respond_with #response
end
end
JS
#app/assets/javascripts/jquery.livesearch.js
// Author: Ryan Heath
// http://rpheath.com
(function($) {
$.searchbox = {}
$.extend(true, $.searchbox, {
settings: {
url: 'search',
param: 'search',
dom_id: '#livesearch',
minChars: 2,
loading_css: '#livesearch_loading',
del_id: '#livesearch_del'
},
loading: function() {
$($.searchbox.settings.loading_css).show()
},
idle: function() {
$($.searchbox.settings.loading_css).hide()
},
start: function() {
$.searchbox.loading()
$(document).trigger('before.searchbox')
},
stop: function() {
$.searchbox.idle()
$(document).trigger('after.searchbox')
},
kill: function() {
$($.searchbox.settings.dom_id).fadeOut(50)
$($.searchbox.settings.dom_id).html('')
$($.searchbox.settings.del_id).fadeOut(100)
},
reset: function() {
$($.searchbox.settings.dom_id).html('')
$($.searchbox.settings.dom_id).fadeOut(50)
$('#SearchSearch').val('')
$($.searchbox.settings.del_id).fadeOut(100)
},
process: function(terms) {
if(/\S/.test(terms)) {
$.ajax({
type: 'GET',
url: $.searchbox.settings.url,
data: {search: terms.trim()},
complete: function(data) {
$($.searchbox.settings.del_id).fadeIn(50)
$($.searchbox.settings.dom_id).html(data.responseText)
if (!$($.searchbox.settings.dom_id).is(':empty')) {
$($.searchbox.settings.dom_id).fadeIn(100)
}
$.searchbox.stop();
}
});
return false;
}else{
$.searchbox.kill();
}
}
});
$.fn.searchbox = function(config) {
var settings = $.extend(true, $.searchbox.settings, config || {})
$(document).trigger('init.searchbox')
$.searchbox.idle()
return this.each(function() {
var $input = $(this)
$input
.keyup(function() {
if ($input.val() != this.previousValue) {
if(/\S/.test($input.val().trim()) && $input.val().trim().length > $.searchbox.settings.minChars){
$.searchbox.start()
$.searchbox.process($input.val())
}else{
$.searchbox.kill()
}
this.previousValue = $input.val()
}
})
})
}
})(jQuery);
#app/assets/javascripts/application.js
//Livesearch
$(document).ready( function() {
var base_url = window.location.protocol + "//" + window.location.host;
$('#SearchSearch').searchbox({
url: base_url + '/search/',
param: 'search',
dom_id: '#livesearch',
loading_css: '#livesearch_loading'
})
});

Read cross domain JSON response

<script>
$.ajaxSetup( {contentType: 'application/json'} );
function submit_data(f){
alert('submitting')
var data_string = $(f).serialize();
$.ajax({
url: "http://localhost:3000/application/1/contact_us.json?jsonpcallback=?"+data_string,
dataType: "jsonp",
type : 'post',
processData: false,
crossDomain: true,
contentType: "application/json",
jsonp: false,
jsonpcallback: result()
});
}
function result(){
alert('back in')
alert(data)
}
function jsonp1300279694167(){
alert('dhoom')
}
</script>
I have script above querying across domain and posting data within a form.
Everything seems to work fine. JSON response can be seen in the firebug console. I want to process the response and display status messages accordingly to the user. How should I achieve it?
UPDATE
I have tried as suggested by T.J. Crowder but have no luck yet. The modified code is as below
function submit_data(f){
alert('submitting')
var data_string = $(f).serialize();
$.ajax({
url: "http://localhost:3000/application/1/contact_us.json?"+data_string,
dataType: "jsonp",
crossDomain: true,
success: handleSuccess()
});
}
function handleSuccess(data) {
alert("Call completed successfully");
alert(data);
}
This does not accesses data and alerts undefined. If I try to pass it from success: handleSuccess() it errors and redirects with a http request.
I am getting response from a Ruby on Rails application. Here is the method I am hitting
def create
errors = ContactUsForm.validate_fields(params)
logger.info errors.inspect
if errors.blank?
respond_to do |format|
format.json {render :json => {:status => 'success'}.to_json}
end
else
respond_to do |format|
format.json {render :json => {:status => 'failure', :errors => errors}.to_json}
end
end
end
Is there any thing that I need to configure in my rails app
You're close. You just use the success callback as usual (see the ajax docs), not a special one:
$.ajax({
url: "http://localhost:3000/application/1/contact_us.json?jsonpcallback=?"+data_string,
dataType: "jsonp",
type : 'post',
processData: false,
crossDomain: true,
contentType: "application/json",
jsonp: false,
success: function(data) {
// Use data here
}
});
Also, your code:
jsonpresponse: result()
...would call the result function and then use its return value for the jsonpresponse property of the ajax call. If you want to use a separate function, that's fine, but you don't include the (), so:
$.ajax({
url: "http://localhost:3000/application/1/contact_us.json?jsonpcallback=?"+data_string,
dataType: "jsonp",
type : 'post',
processData: false,
crossDomain: true,
contentType: "application/json",
jsonp: false,
success: result
});
function result(data) {
// use `data` here
}
Also, I'm pretty sure you don't need/want the jsonp parameter if you use success, so:
$.ajax({
url: "http://localhost:3000/application/1/contact_us.json?jsonpcallback=?"+data_string,
dataType: "jsonp",
type : 'post',
processData: false,
crossDomain: true,
contentType: "application/json",
success: result
});
function result(data) {
// use `data` here
}
Finally: Are you sure you want to set contentType? That relates to the content being sent to the server, not the content being received from it. If you're really posting JSON-encoded data to the server, great, you're fine; but it looks like you're using jQuery's serialize function, which will not produce JSON (it produces a URL-encoded data string). So you probably want to remove contentType as well, both from the call and from the ajaxSetup call.
I hope if you can try jQuery-JSONP
jQuery-JSONP How To
[Example]
$.getJSON('server-url/Handler.ashx/?Callback=DocumentReadStatus',
{
userID: vuserID,
documentID: vdocumentID
},
function(result) {
if (result.readStatus == '1') {
alert("ACCEPTED");
}
else if (result.readStatus == '0') {
alert("NOT ACCEPTED");
}
else {
alert(result.readStatus);
}
});
I tried many tutorials including the answers above but had no luck. So I implemented it something like below
Form
<form action="" onsubmit="submit_data(this, '1'); return false;">
// some form fields
</form>
Submit function for form
<script>
function submit_data(f, app_id){
var data_string = $(f).serialize();
$.ajax({
url: "http://www.example.com/"+app_id+"/contact_us.js?"+data_string,
dataType: "jsonp",
crossDomain: true,
});
}
function show_errors(jsonOb)
{
$("span.error").remove();
$.each(jsonOb, function(key,val){
$("#contact_us_form_"+key).after("<span class=error>"+val+"</span>")
});
}
</script>
In my controller
def create
#application = Application.find params[:application_code]
#errors = ContactUsForm.validate_fields(params, #application)
#application.save_contact_us_form(params[:contact_us_form]) if #errors.blank?
respond_to do |format|
format.js #{render :json => {:status => 'success'}.to_json}
end
end
And finally in create.js.erb
<% if #errors.blank? %>
window.location = "<%= #application.redirect_url %>"
<% else %>
var errors = replaceAll('<%= escape_javascript(#errors.to_json)%>', """, "'")
var errors_json = eval('(' + errors + ')')
show_errors(errors_json);
function replaceAll(txt, replace, with_this) {
return txt.replace(new RegExp(replace, 'g'),with_this);
}
<% end %>
This way I called submit_form on form submit and called show_errors javascript function from server it self. And it works..
But still I would like to have comments if this is a worst solution?

Resources