I'm having a little trouble using jQuery in Rails.
I'd like to call the destroy method for a specific list item and then remove it from the list via ajax. My code is pretty simple:
# _web_profile.html.erb - The partial containing the link to destroy:
<%= link_to 'Remove', web_profile, :method => :delete, :class => 'remove_button' %>
# The ajax hijacking
$('.remove_button').live('click', function() {
$.ajax({ type: 'delete', url: this.href });
return false;
});
# In my controller
format.js { render(:update) { |page| page.remove "web_profile_#{id}" } }
Ok, basicly that's it. When I press my button everthing works fine, but instead of executing the script I'm getting it a text output in the browser:
# Browser output
try {
jQuery("#web_profile_12").remove();
} catch (e) {
alert('RJS error:\n\n' + e.toString());
alert('jQuery(\"#web_profile_12\").remove();'); throw e
}
Any idea why this nice javascript code isn't executed? I tried to add dataType to the ajax request already.
Thanks and best regards,
Joe
Why "type" is set to "delete"? It should be set to "POST" and dataType to "script". Because of cross browser compatibility isssues, a workaround should be used to specify action as the RESTful delete. The data should have "_method" parameter set to "delete".
I suggest to install jrails and use link_to_remote and other Rails helpers. They generate correct JS code. So you can learn how to build correct Rails Ajax requests.
You have to either set the type to "script" in the $.ajax() call, or replace that call with $.getScript().
Related
I rely on a variable defined by a JavaScript object to update (re-render) a Ruby on Rails partial inside a view, but it is not working. From all I read it tells me the only possible way for this to work is to use an Ajax call, however I'm new to this and couldn't quite grasp why (given the JavaScript variable is available before the Rails command is defined), nor exactly how I should do it.
My Rails view has this HTML bit I'm looking to
<div id="myevent">
<% if #pocket.events.any? %>
<%= #event = #pocket.events.first %>
<%= render #event %>
<% end %>
</div>
On that same view I implement a JavaScript object made up of various clickable nodes, each node denoting an event with a unique id. I want to re-render the #myevent section above each time someone clicks on a different node.
As someone new to front-end programming, I've tried this:
timeline.on('click', function (properties) {
logEvent('click', properties);
var item = properties["item"];
$('#myevent').html("<%= escape_javascript render (#pocket.events.find_by id:" + item + ") %>");
The JavaScript variable 'item' contains the event id from the clicked node. As you may know, the last line above doesn't work, Rails raises an ArgumentError with the message:
'nil' is not an ActiveModel-compatible object. It must implement :to_partial_path.
It does works if I set a hardcoded id, so the rationale seems to be right:
$('#myevent').html("<%= escape_javascript render (#pocket.events.find_by id: 5) %>");
I have tried using the partial format as well:
$('#event').html("<%= escape_javascript render :partial=>'shared/event', :locals=> {event_id: " + item + "} %>")
And then using the following method to recover the right event in the 'shared/event' partial:
<%= #event = Event.find(event_id) %>
however, it fails with a ActiveRecord::RecordNotFound error because it doesn't really replace the string "+ item +" by the value of that JavaScript variable:
Couldn't find Event with 'id'=+item+
The "pain" is that, no matter how I try I can't find a way to use the JavaScript defined variable on that Rails calls. It looks like the solution would be to use an Ajax call but I reckon after many tries I don't get to work this out by myself. I would really appreciate a hand here.
I think that what happens is that the .erb portion of the code is only parsed at render time, so when the page first renders, there's really no item to append to that. In turn, when you hardcode it, it's able to parse that ruby code and render correctly.
So indeed you need to contact the server.
What I would do would be:
timeline.on('click', function (properties) {
var item = properties["item"];
$.ajax({
type: "POST",
url: '/route_to_your_controller',
data: { event_id: item },
dataType: 'json',
success: function(data) {
if (data['success']) {
$('#myevent').html(data['html']);
}
else {
alert('oopsss take care of this');
}
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
console.log(errorThrown);
}
});
});
routes.rb
post '/route_to_your_controller', to: 'controller#spew_html'
then, saying you have a route to this action on a controller:
def spew_html
# you should take care of failed finds, errors that might occur, have a before_action to check legality of ajax call, and even possibly accept only xhr, return false unless request.xhr? etc
event_id = params[:event_id]
html_partial = render_to_string 'shared/event', locals: { event_id: event_id }
respond_to do |format|
format.json { render json: { html: html_partial, success: true } }
end
end
As a bonus, one pattern I like to implement when possible is just having the same route for GET be the POST for whatever ajax, so that then I can simply do:
$.ajax({
type: "POST",
url: window.location.pathname,
//...
});
And let the controller know how to deal with it.
I'm having a lot of trouble trying to do something that I imagine would be fairly simple.
I have a list of items, let's say, todos. At the bottom of that list I have a text field where I add new items to that list. I want to make it so that the new items are added to the bottom of that list dynamically, without a full page refresh, like in a chat window.
I made the submit form remote: true and it successfully submits without reloading the page, but I can't get the new item to appear at the bottom of the list at the same time. I have to refresh the page to see the changes.
I tried a few different approaches I found on SO (there's no shortage of similar questions here) and the web, and even a gem called Sync, but each of them had errors and problems of their own and I couldn't get any to work properly. Each of them could be its own SO question. So instead I ask: Is there a "recipe" that is sure to successfully implement this in Rails 4?
let's say, now you have a user form to submit,
<%=form_for #user,remote: true%><%end%>
And you also have a controller,
UsersController
In your controller, you have a function,
def create
#something
end
which is for the form.
the only thing you need is to modify the function like
def create
#something
respond_to do |format|
format.js
format.html
end
end
then in your view side, under directory of view/users/ , create a create.js file, in the file, you can do the js action, like get the new record, and append the new record to the users list.
reference:
http://edgeguides.rubyonrails.org/working_with_javascript_in_rails.html#form-for
There are various ways to do what you are asking. My approach would be:
Create an AJAX call to the controller that passes the parameters of the form
Inside the controller, you save/update things and then return a JSON object
On the success callback of the AJAX function, you append a list item/table row, using the object values
The code could be something like this:
model.js
$(function() {
$("#submit_button").on("click", function(event) {
event.preventDefault();
$.ajax({
type: "POST",
url: "your_controller_url",
data: "your_form_data"
success: function(result) {
// Append the result to a table or list, $("list").append(result)
},
});
});
});
controller.rb
def your_action
# Do your stuff
# return JSON to the ajax call
end
Well, this is just a skeleton. I prefer doing things this way. (Because i hate the js.erb approach)
Here is rails 5, hope it will help someone ( it still works on rails 4 ):
Try this ajax example:
In 'routes.rb':
# set the route that ajax can find the path to what controller in backend
get '/admin/some_great_flow', to: 'great_control#great_flow'
In 'great_control_controller.rb' controller:
# this function in controller will response for ajax's call
def great_flow
# We can find some user or getting some data, model here.
# 'params[:id]' is passed by ajax that we can use it to find something we want.
#user = User.find(params[:id])
# print whole data on terminal to check it correct.
puts YAML::dump(#user.id)
# transform what you want to json and pass it back.
render json: {staff_info: #user }
end
In 'app/views/great_control/index.html.erb' view:
<div>
<label>Staffs</label>
<%=select_tag(:staff, options_from_collection_for_select(#staffs, :id, :name), id:"staff_id", required: true)%>
</div>
<script>
//every time if option change it will call ajax once to get the backend data.
$("#staff_id").change(function(event) {
let staff_id = $("#staff_id").val()
$.ajax({
// If you want to find url can try this 'localhost:prot/rails/info/routes'
url: '/admin/some_great_flow',
type: 'GET',
dataType: 'script',
data: { id: staff_id },
// we get the controller pass here
success: function(result) {
var result = JSON.parse(result);
console.log(result['staff_info']);
// use the data from backend for your great javascript.
},
});
});
</script>
I write it for myself.
You can see the changes using javascript.
For eg lets consider a controller Mycontroller with action index and you are submitting form on index.
Then create a file in views my_controller/index.js.erb
To reflect changes use javascript in this template.
Definately remote sends the ajax call, so to see the changes you need some manipulation using javascript.
Thanks
Check this out. I've got a fairly simple form that's created with the following syntax:
<%= form_for([#issue, #issue_order], :remote => true) do |f| %>
The form, due to logic on the page, is actually called via javascript, like this:
$('#new_issue_order')[0].submit()
The controller handles the ajax request by doing a bit of logic then throwing out a little something like this:
respond_to do |format|
format.js
end
The AJAX that handles this response is in the following javascript:
$('#new_issue_order').on('ajax:success', issueOrder.processOrder)
..........
processOrder: function(e, data, status, xhr) {
$('.sign-up-errors').empty();
errors = xhr.getResponseHeader('X-Flash-Error').split(',');
for (i=0; i < errors.length; i++) {
$('.errors').append($('<p>' + errors[i] + '</p>'));
}
setTimeout(function() {
$('.errors').empty();
}, 3500);ยท
}
I figured that would allow it to respond to the remote request, but what I get instead is the following error:
ActionController::UnknownFormat
I tried creating a new.js.erb in my views (to correspond with the new page that it was on), but I'm still getting the same error. I haven't tried migrating my success handler AJAX to the new.js.erb code, because I'd prefer to keep my javascript handling in the javascript file in my assets for business reasons.
How can I get a seamless AJAX response? I've done it before, but respond_to has always confused me.
Setting the js response template as new.js.erb is incorrect.
The form itself is within new.html.erb template, guessed by convention. So, the form's action is supposed to point to #create.
In order to response correctly to this form's submission, you need to create a js template as create.js.erb, and respond to js in #create action.
Besides, in most cases you don't need to manually set Ajax response in assets js like
$('#new_issue_order').on('ajax:success', issueOrder.processOrder)...
Instead, you can just do it within create.js.erb. For example
$('#new_issue_order').css('background', 'green')
This script will be run after ajax:success event.
I am trying to update a div in my rails application. Iam just learning ROR. So Its a learning stage for me.Please find the code.
In view page...
<%= javascript_tag do %>
jQuery(function($)
{
$("#tabs4").click(function()
{
$.ajax(
{
url:'/spaces/showcal',
type:'GET'
});
});
});
<% end %>
In spaces controller..
def showcal
respond_to do |format|
format.html
{
render (:update) { |page|
page.replace_html 'tab4', :partial => 'spaces/showcal'
}}
end
end
What am I doing wrong here.. Please help
I also have a partials page (_showcal)which has some text to display into that div.
When using ajax, a good way to debug this kind of thing is see what response the server returns. If it looks good, then you know your javascript code needs to change to get it working, if it looks wrong, then fix the server side code first.
Here's how i would do it:
def showcal
render :layout => false
end
#showcal.html.erb
<%= render :partial => "spaces/showcal" %>
Your js block:
jQuery(function($)
{
$("#tabs4").click(function()
{
$.ajax({
url:'/spaces/showcal',
type:'GET',
success: function(data)
{
$('#tab4').html(data);
}
});
});
});
I generally prefer using javascript as supposed to rails built in code when it comes to replacing and using content with ajax. I have to admit, because of this, I'm not sure if your update action was mostly correct or not. The page.replace_html should work if you're using the prototype helpers. The only thing is because you are doing an ajax request to achieve it and the resulting body would contain that code, and it would not execute on your current dom. So i think that was probably executing, but because it's on a separate page response, it didnt do anything.
Give my suggestions a try, and let me know how it goes. Sorry that my answer is a bit hazy.
You should request /spaces/showcal.js and react to format.js.
I never worked with $.ajax, you might need to set additional parameters there. With script.aculo.us, something like this works:
new Ajax.Request('/tasks/#{task.id}.js',
{asynchronous:true, evalScripts:true, method:'get'});
I've got the following form:
<%= form_for [#commentable, Comment.new], :remote => true do |f| %>
<%= f.text_area :body %>
<%= f.submit "Add your comment" %>
<% end %>
Then the controller (heavily stripped down to the basic part):
def create
respond_with(#comment) do |format|
format.html { redirect_to params[:return_url] }
format.json { render :layout => !request.xhr? }
end
end
Then here is the javascript for handling the form AJAX:
$('#new_comment')
.bind('ajax:success', function(evt, data, status, xhr){
var $this = $(this);
// Append response HTML (i.e. the comment partial or helper)
$('#comments ol').append(xhr.responseText);
$('#comments ol li:last-child').effect("highlight", {}, 3000);
// Clear out the form so it can be used again
$this.find('input:text,textarea').val('');
// Clear out the errors from previous attempts
$this.find('.errors').empty();
})
.bind('ajax:error', function(evt, xhr, status, error){
// Display the errors (i.e. an error partial or helper)
$(this).find('.errors').html(xhr.responseText);
});
The form submits fine and the comment gets appended as it should, but Safari's Web Inspector shows a SyntaxError: Parse error on line 1 (which is just the doctype) of whatever page I'm on when the comment form is submitted, and I can't figure out why.
Not even sure where to start with this one.
I have gotten that error and it was because the remote ajax call was expecting JavaScript and I was returning html. The .ajax call was defaulting to dataType 'script' so when it got the ajax result, it tried to evaluate it and any < tags caused the error.
I got rid of the error by changing the view from .js.erb to .html.erb and adding 'data-type' => 'html' to the link that contained :remote => true.
In some cases, it may make sense to have the form itself be a JavaScript snippet - something like:
$('#container').replace("<%= escape_javascript(render(...)) %>");
Then you can omit the data-type from the link.
More information about data-type here.
Try putting your bind call on the same line as the new_comment reference:
$('#new_comment').bind('ajax:success', function(evt, data, status, xhr){
I think you're looking at the wrong line 1 - are you including your JavaScript files, it might mean line 1 of one of them. It might also mean line 1 of what's being sent back.
To be honest - try Chrome and switch on the debugging, Chrome is much better at saying which file gave it the problem and where.
You don't need to put all the JQuery in your file anyway - you can write a response in a view if you have the right gems installed - not sure if that would help.
Do you have a div on the page with class "errors"? I was having a very similar problem, and found this question while searching for a solution.
My issue was that we were trying to write back to a div that didn't exist (because of an unless .empty? on the page). When we solved that, the success callback worked.