#136 jQuery & Ajax (revised)
Here I go back to the basics and show how to turn a traditional Rails app into one that interacts through Ajax by adding "remote" options, JavaScript templates and a dabble of jQuery.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
Great episode, thanks!
There is one more thing on jQuery i'd like to know about: why sometimes in development mode remote scripts executes twice (like double text_form submits) when 'requre tree .' is enabled?
I had this problem when running on the same machine in production environment, because the assets are precompiled, and thus are served twice.
You should change the assets dir on the dev environment + bundle exec rake tmp:clear to clear the precompiled assets.
to solve this issue, I add "config.serve_static_assets = false" to my config/environments/development.rb
Also, an interesting related talk from LA RubyConf about Rails, JQuery and Unobstrusive'ness.
After my recent issue with
document.getElementsByName
in Internet Explorer, I am now a strong advocate of using jQuery whenever possible.Thanks for revising this. More episodes along these lines please.
+1
Awesome episode Ryan! Especially for a JS phobe like me!
Does anyone know of any good books that teach you how to use JS/jQuery/CS with Rails from scratch?
Noel Rappin has a series of self published books on Javascript and he happens to use Rails as the server. They are fairly decent:
http://www.noelrappin.com/
Hope this helps
Thanks - will check them out :)
Thanks for the revision!
also check the
.live
method in jQuery. from the doc:so you can do something like
That's been deprecated. Use .on() or .off() if you can.
The preferred method would be to use event delegation using .on() and bind a single click handler to the parent element $('.tasks') and then check the event target to determine the appropriate behavior. This would handle new tasks added after the initial load and would only require a single event binding, rather that one for each task.
+1 Very efficient. I hope Ryan can update the log to include this. This comment is very useful, and prolly missed by a lot of people.
Is there an issue in _task.html.erb where clicking on the label element for each task checks/un-checks the completed box for the first task in the list, or did I miss something?
What's the best way to assign a unique name to the checkboxes for each task?
This is what I came up with that seems to work:
_task.html.erb:
<%= formfor task do |f| %>
<%= f.check_box :complete, id: "task_complete#{task.id}" %>
<%= f.submit "Update" %>
<%= f.label "complete_#{task.id}", task.name %>
<%= link_to "(remove)", task, method: :delete, data: {confirm: "Are you sure?"}, remote: true %>
<% end %>
Thanks!
Altho you forgot an underscore on your first line
I keep getting this message for every request to webrick:
Started GET "/assets/jquery_ujs.js?body=1" for 127.0.0.1 at 2012-09-29 13:24:45 -0500
Served asset /jquery_ujs.js - 304 Not Modified (0ms)
[2012-09-29 13:24:45] WARN Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true
Does not seem to do any harm. Would anyone know what is it and how to get rid of the warning?
Bharat
This is a known bug with webrick, you can ignore it, or use a different server, more here on stackoverflow: http://stackoverflow.com/questions/9612618/warn-could-not-determine-content-length-of-response-body-set-content-length-of
Thanks. It works fine in Thin as suggested in the tread.
I would love to know about validations, that's where I had difficulty. Particularly say if the task must be over 5 characters long, how do you show that error by inserting it dynamically into the original create form.
Watch the Client Side Validations railscast: http://railscasts.com/episodes/263-client-side-validations
In case anyone is interested: I just released a gem for "ajaxifying" a rails app: https://github.com/ncri/ajaxify_rails
Thanks - gonna check that out!
That is an amazingly easy gem to use.
This is excellent - it removes so much of the hassle. Thanks for sharing, I'm definitely going to be using this.
Did you see turbolinks? Looks like you are developing the same thing.
really bummed ryan didn't show the best way to handle events bound to new elements added to the dom. Although it seems simple to add a function the better and more generally accepted best practise would be to use the .on event Delegation. example:
+1, thought so too (somebody mentioned it higher up in the comments already though).
Perhaps because he wanted to demonstrate how simple it is to write "jQuery Plugins"?
Your solution is admittedly more mainstream, but is it more generic?
Seeing how to writing a plugin is nice, sure, but its just not the best solution here imo. Using "on", you don't need to worry about adding any event handlers later.
There's a need to create a plugin to call .remove() on Update button. So maybe there's not much difference what method to use here anyway.
.on() is the preffered way to add events on new elements.
Ppl used to rely on .live() which would parse the whole page for all elements matched in the selector. This was causing heavy client side cpu usage.
Sadly, that is only available to 1.7+
I agree. I requested an ajax episode hoping that newer methods for binding would be demonstrated. I was also hoping that binding events through the ajax cycle would be covered. I've been trying to bind events to show a spinner user .on and then using the ajax events at the different points in the ajax call life cycle. It seems there's ways to do this with UJS and ways to just do it with jQuery now as well. I was hoping for coverage on that. Disappointed though that at least the new proper ways to do event bindings weren't covered.
OK. I do not know much about the newer method. But the following works. It comes right out of Ryan's code on the RailsCasts itself which he has kindly open-sourced here:
https://github.com/ryanb/railscasts
In order to show a spinner on a page, you need to first create a div like so:
<div id="spinner" class="spinner">
<img id="img-spinner" src="/assets/spinner.gif" alt="Loading"/>
</div>"
Then in the document ready function, insert the following code:
$(document).ready(function(){
$("#spinner").bind("ajaxSend", function() {
$(this).show();
}).bind("ajaxStop", function() {
$(this).hide();
}).bind("ajaxError", function() {
$(this).hide();
});
});
The code is not mine, as I pointed out above, it comes from the RailsCasts code. I have tested it and it works as explained above. If this is not what you are looking for, please ignore.
Also, remember to do not use, jQuery $(function(){}); (document ready), just leave your js file at the bottom, which is the best practice.
Any hints on why not to use dom ready? Links?
Please explain how is that the best practice
Thank you so much. Basics for some, perhaps, but I such a solid and great explanation, this was very helpful.
I also appreciate your using regular JavaScript/JQuery for the example, rather than CoffeeScript; the sleep() tip is nice as well :]
Kudos for this!
A note on the destroy action: You should really use Rails'
dom_id
helper method here because AFAIK that's what all of Rails' other helper methods (e.g.content_tag_for
,form_for
etc.) use. So the code would be like this:Thanks for mentioning dom_id. I wrote my own helpers for this. I can now go remove code. :)
I really find it useful! But I'd also like to know how'd you test it.
and if we wanna handle the validation after submit the form in a case there is some errors? whta to do? someone have a nice tutor about it?
Remember that you're sending back a "js.erb" view template. This just renders out the _task or form. So if you already had error message handling, it will just work. Try it!
Does the new action automatically call both the html.erb and js.erb?
Unless you're explicitly handling particular formats (for example you have a respond_to block in your action that only handles the html format), then Rails will automatically look for a view that matches the incoming requested format. So if you have the following in your controller:
And the incoming request format is
js
, Rails will look for a matching js view to render (ie. new.js.erb). If the request format ishtml
, then Rails will look for a matching html view to render (ie. new.html.erb).http://guides.rubyonrails.org/layouts_and_rendering.html and http://guides.rubyonrails.org/action_controller_overview.html are good resources for understanding ActionController and ActionView.
+1
Kudos!
I'm having the same issue. For some reason it seems that my
new.js.erb
file is not getting loaded. Mynew
action in my controller does not have a respond_to block, although the create action does.Why is it not loading?
Kind of a noob question here, but when Ryan moves the javascript into $(function() { }, why does this fix that issue? Would using the standard $(document).ready() not do the same thing?
Yes. It's just shorthand.
Which is considered "best practice"
1. Returning the partial via javascript (.js.erb) OR
2. Using an ajax call back function to update the page on success or failure
Thank you for your comments.
To this question I would also like to know the answer. Can somebody help?
Better solution is to use ajax. It's easier to read now and programmer knows what's going on.
When you are using the ".erb" code, it out of context and there are two different places.
But how do you render something in full JS ? Using render_to_string and sending a JSON ? An other solution or example ?
I have tried to follow this tutorial to implement it in my own project, but I am never able to get it work. After adding the new.js.erb and adding the js code shown in the video, when I click 'Add Post' link nothing happens. I have followed the trouble shooting steps recommended by Ryan, still no dice. Although, I did get a n/w status of 304 unmodified as opposed to the 200 ok that Ryan got in the video. Any ideas as to what I am doing wrong?
Hi Daniel , had the same problem. In your controller if you have a respond block you have to add respond_to js. If you dont have a respond block it will look for js.erb automatically.
I've also downloaded the "before" example from Ryan's github page and am trying to work towards the "final" result when i click the new link it redirects to the new page as opposed to letting JQuery do its magic. Does any body have any suggestions?
Got a question here.
I tried to follow the tutorials and when I got to the step:
$(document).ready{
$('.edit_task input[type=submit]').remove();
$('.edit_task input[type=checkbox]').click(function() {
$(this).parent('form').submit();
});
});
It just does not work for me.... i tried to find out why using the checklist-after version and that would not work as well... any thoughts on why this is happening ????
In my create.js.erb, I have this:
$('.lkuptbody').append('<%= j render(@lookup) %>');
When I try to create a new lookup I get an error. The error says "Missing partial lookups/lookup." Didn't Ryan say that render is coming from the controller? Why is it looking for the partial instead?
Like others who commented here I have been putting off learning js as long as possible. Rails is friendly, js is scary! But not anymore... Thanks Ryan! This really helped me. So much to learn still, but I have a lot more confidence now!
I'm pretty much a noobie when it comes to writing jQuery and am trying to fade out an item as it is deleted. I have tried the following in my destroy.js.erb file - the item is deleted but does not fade away so stays on the page until the page is reloaded.
If I take out the fadeOut part, it successfully disappears from the page.
Would someone mind pointing me in the right direction?
You need to hook the remove method into a callback function which is executed once the fadeOut method finishes, like so:
Thank you so much :)
no problem, i remember that biting me in the butt once too :/
words cannot explain how useful I have found this episode!
I've restructured my first rails site to work this way.
I hit a snag that's bugging me.
For example, in this file:
https://github.com/railscasts/136-jquery-ajax-revised/blob/master/checklist-after/app/views/tasks/_task.html.erb
The delete action is fired with this link:
<%= link_to "(remove)", task, method: :delete, data: {confirm: "Are you sure?"}, remote: true %>
I have incorporated CanCan (another mega thanks, btw!), but this link doesn't work. The user not set when it is passed this way, so cancan authorization fails. I can get around it by using a form and submit instead of the link... but i feel like i'm missing something???
I posted my question here, but thought I'd go straight to the masters for this one...
http://stackoverflow.com/questions/15815522/ruby-on-rails-cancan-user-is-nil-during-destroy-action
Brilliant screencast. I expected some basics but you even went as far as developing jQuery plugins. And you show common pitfalls. Thanks so much.
Is there a way to edit the name?
I implemented this with my existing Projects views, creating a separate _tinyform partial so I could still edit each Project.
Problem: the submit button is gone, so my users cannot edit their Projects.
What do I do? Can I turn off the plug-in? Is that one of the advantages of using .on, perhaps?
Thanks! I'm a newbie in rails and especially in js.
There is a typo in the ASCII cast:
rename title from: /app/views/templates/update.js.erb
to: /app/views/tasks/update.js.erb
Thanks for reporting this error in the ASCIIcast, I have corrected it.
Also the new.js.erb title incorrectly has the view/templates directory and should be /app/views/tasks/new.js.erb instead of /app/templates/tasks/new.js.erb
e.g.
$('#new_link').hide().after('<%= j render("form") %>');
I have been trying to understand this code for the past few hours
$('#edit_task_<%= @task.id %>').appendTo('#complete_tasks');
From where do we get the "edit-task-" div ? Please help
Never mind.. I just understood it.
Great screencast! One issue I am running into however, is the submit buttons reappearing when returning back to the page after having left. Any ideas on a fix?
Noob question. I'm confused about the first line in the new.js.erb file where he selects an element "#new_task". Where is this element located? I looked through the source code and couldn't find anything. Is there a Rails convention for this that I should be aware of?
People, I am having a little problem, I think that is because de turbolinks, when I click in the new task link more than one form is showed to me, anyone can help me? when i disable turbolink, everything it's OK
I see render @incompletetasks, but I didn't see the _incomplete_task.html.erb ? I'm using Rails 3.2.13 and give me missing template error==
try it with _task.html.erb ;-)
How does this work? I am having the same issue getting a Missing Template.
Rails looks at the collection object, figures out what kind of object is is, and automagically looks for a partial by that name.
For example, if the controller set us an instance var like so:
@completed_tasks = Task.where(complete: true)
Rails goes like:
So you want me to render this bad boy? What kind of instance var is that? It then figures that it is a "Task" collection, then it assumes that you must have a partial named "_task" that will display it.
Silly Rails...
I would have liked to see validations using AJAX. If the user tried to submit a blank task, I'd want the field highlighted and an error message to be displayed. I'll figure it out but it would have been nice to see the best practices related to this.
Is there no in-DOM solution for establishing a target element to load the response? For example:
<%= link_to "Show panda", "images/panda.gif", {remote: true, data-target: '#panda-holder' %>
Then every remote link could call something generic like:
$('[data-remote=true]').on('click', function (e) {
e.preventdefault();
$($(this).attr('data-remote')).load($(this).attr('href'));
});
Or it could be more elegantly worked into rails.js. This avoids splitting ajax behaviour into dozens of .js.erb files, and writing format.js for every method in the controller. Sure wish something like that was built into rails.js.
Turbolinks is a great solution and I'm using it in a re-write, but for now I have to deal with a rails 3.1.10 app with loads of
.on
ande.preventdefault
s in application.jsHey,
Does the example repository in this cast work anymore? I downloaded the code from GitHub and ran the "checklist-after" and the request for a new task reloaded the whole page, not just the form with AJAX.
Cheers
Would it be possible to use this with nested items?
IE I have a view setup with many Todo Lists and I'd like to be able to add Todo Items under those lists dynamically. (basically how a todo list works at Basecamp)
I have the proper associations taken care of, so I can add Todo Items to Todo Lists via the HTML form, but can't figure out how to pass the
todo_list_id
through jquery to use the remote option.After all of these years, no one ever noticed complete(comlpete) is spelled wrong :>)
Good lesson though.
Really wish I had watched this a long time ago. Sorted out a bunch of muddled thoughts in my head. Superb
If you have an issue with assets compiling as I did--no css or javascript (eg. no alert box on destroy) solution is to bump up Rails version to 3.2.14 and run bundle install.
Details are here:
http://stackoverflow.com/questions/18005115/why-are-js-files-in-my-rails-asset-pipeline-not-being-compiled
(Interesting to learn how destroy requires Javascript to get a delete request (Started DELETE). )
This article has been updated to Rails 5 as a blog post Using Ajax and jQuery in Rails 5 Apps
this is awesome