Skip to main content
60

Button Loading Animations with jQuery UJS

Episode 16 · July 21, 2014

Learn how to easily disable the submit button and display a loading animation when a user submits the form

Frontend Javascript


Code

Standard Rails

<%= f.button "Sign In", class: "btn btn-success", data: {disable_with: "<i class='fa fa-spinner fa-spin'></i> Signing in..."} %>

SimpleForm

<%= f.button :button, "Save Post".html_safe, data: {disable_with: "<i class='fa fa-spinner fa-spin'></i> Saving..."} %>

Resources

Transcripts

The combination of rails and FontAwesome CSS library allow us to easily drop in loading animations into all of our form buttons. This is something that I think that almost every developer should be doing, and it's actually super simple, we just need to take some time and put this into our applications by default. If we take a look at the examples on FontAwesome's website, and we scroll down to the spinning icons, this is what I'm talking about, we have this loading animation, that basically says: Hey, the website is processing, let us do our work, and don't start getting ahead of yourself, so if we're processing a credit card, we want the user to wait a second, because we've got to talk to Stripe, we've got to let them check the credit card validation, make sure they can charge the card, and then get back to us, and then we can finally get back to the user and say: Hey, your card was successfully charged. These loading animations are very important for things like this, and we're starting to do more complex stuff in our applications.

Most of the forms that I see are created with the f.submit tag, and when you inspect the form that is generated, you can take a look at the button, and you'll see that it creates an input tag, has my classes, has a type of submit, and then the value is the actual text of the button. When we right click on this, we can say "edit as html", but it's editing the whole button. If we try to put an i tag in here in the value, we can see that it gets escaped by browser, which of course it should, because these input type submit tags are not intended for this, they're not intended to have html inside the content of the button, they're just very simple submit button, and that's basically all they're intended for. If the input type submit isn't going to work for what we want, we can change this to a button tag, and if we inspect this page again, we'll see here that the button actually has this sign-in text inside of the button tag, so the button wraps this text, it means that we can edit as html, and we can put in some html in here, and we'll see that now we have an italic letter "a", so that is working, and it does allow us to have html in it. Now, the only other thing that we have to solve is: When you click on this button, we need it to actually change immediately before the form gets submitted, and then insert that html, and preferably disable the button as well, and UJS to the rescue. jQuery UJS that ships with rails comes with functionality that allows us to do this automatically, and we don't have to write any of the code ourselves. All we have to do is add a data attribute onto this button tag. This works very similarly to when you have a link_to, and you write method: post or method: delete, so this will add the attributes to the link, and jQuery UJS will interpret that. With the thing that we're trying to do here, we want to add a data: {disable_with:""}, and this is going to be the string of the contents of the button that we want. jQuery UJS will look for any attribute that has data: {disable_with:""} and then it will take the appropriate action when the form gets submitted. Here we can say that we want that icon class is

data: {disable_with: "<i class='fa fa-spinner fa-spin'></i> Signing in..."}

jQuery UJS needs to be in your application.js file, but it should be there by default, so you'll have jQuery and jQuery UJS, and you shouldn't have to worry about that. As long as those are in there and being loaded on the page, when you add this data: {disable_with:""} attribute, you'll be able to just drop that in, reload your page, and then now when you click "Sign In", it will disable the button, show the loading animation, and change the text all in one swoop, and you didn't have to write any JavaScript to take care of that, the only changes we did were add this data attribute, and change f.submit to f.button, and we're done, which is really really cool. I highly encourage you to take this and add it to pretty much every single form in your application. If a button just links to another page, you don't really need to say that you're loading another page, but when you're submitting a form, I highly encourage you to make this change, because it really makes the UX feel more intuitive. I definitely encourage you to search for all the submit tags in your application and add this attribute on to them, and if you have any other quick wins like this to improve the UX, definitely let me know, write me an email: [email protected], and I will see you next episode.

Discussion


Fallback
Innokenty Longway

Thank you! Really simple. :)


Fallback

Works also with button_to, but to invoke font awesome [... remove needles spaces in what follows ...] data: {disable_with: raw(" < i class='fa fa-spinner fa-spin'> < /i>One moment please...")}

Fallback

I just fixed the example code in the notes. It was getting automatically removed on accident.


Fallback

Nice tutorial... as always :'D
Thanks!

Fallback

I have a little problem here!
As you can see on my screenshot (http://imgur.com/XMlcVv7), the spin doesn't show itself... And I don't know why...

Here is my code :

<%= f.button "Sign in", class: "btn btn-primary", data: { disable_with: " Connexion en cours..." } %>

I see "Connexion en cours..." but not the spin

I have install font-awesome-rails (gem 'font-awesome-rails', '~> 4.1.0.0')

Thanks for your help!

Fallback

1. Make sure font-awesome is included in your application.css
2. If you put the font-awesome icon on the page outside of the button, does it show up by itself?

Fallback

I have this on my application.css.scss :

@import "bootstrap";
@import "font-awesome";

I found a strange bug... by testing point 2. ^^'

When I add this on my <h2> it works (on my h2 and on my loader)
<h2>Sign in</h2>
But when I remove it, it disappears on both... WHAT THAT'S CRAZY!

Here the screenshots :

with h2 spinner : http://imgur.com/MjsVd5b
without h2 spinner : http://imgur.com/PVJrR5H


Fallback

This post was just in time. I was looking for a dead-simple submit feedback animation. Perfect. :)


Fallback

Chris, thank you so much for this! I've been struggling getting fontawesome to work with my buttons. Perfect timing. Your videos rock!


Fallback

Thank for this nice tip :)

Anyway, how can i acheive this with haml and inside link_to?? I try with:

= button_to "Go Rails!".html_safe, akcept_path, method: :patch, class: 'btn btn-success', data: { disable_with: " Waiting..." in my label instead <button class..="" thx="">

Fallback

You might just be missing the ending curly brace. This works for me:

= button_to "Go Rails!".html_safe, events_path, method: :patch, class: 'btn btn-success', data: { disable_with: " Waiting..."}

Fallback

Fallback

Fantastic stuff. Thanks for your efforts.


Fallback

Fallback

Having a bit of a problem with this. The disable action works, but the icon never shows up. However, if I have the icon already spinning somewhere else on the page first, the icon does show up when I click the button. What gives? Is it not loading fontawesome because there's none of the font there at page load?

Fallback

That's interesting. Do you get any errors in the console that show the browser having trouble loading the icon font by chance?

Fallback

I was getting the basically the same behaviour as James M. I checked the console and there were no error messages.

Seems to be some caching problem with font-awesome. I changed to a css only spinner e.g. http://stephanwagner.me/onl.... This worked find and I could get rid of the font-awesome dependency. Thank you for a useful tutorial.

Fallback

Did you get anywhere with this? Im getting the same issue in Chrome, but its working fine in Firefox.

Fallback

In Safari i'm also getting no Icon and additionally the text fails to change as well.

Fallback

I figured this out, at least for my situation. Once the font is loaded into the cache, I no longer have an issue with the spinner not showing up. If it is not loaded, the spinner eventually shows up where it needs to be if the form submit time is long enough. Is there a way to force a pre-load of the font? In the absence of a workaround, it seems like it might make more sense to just use a small image instead of having to load a whole font...

EDIT: I see no difference in behavior between chrome and firefox.
EDIT2: I'm using Rails 4.1.5 if that matters

Fallback
Yes, this doesn't work in Safari.  Even this page https://gorails.com/users/sign_in does not in Safari.
Fallback

Christoper Drane's solution above is an awesome supplement to Chris' tutorial. I had the same issue - font awesome was not working until after the spinner was cached. I switched to a pure CSS solution per http://stephanwagner.me/only-css-loading-spinner using relative positioning and on a div itself instead of the before pseudo element. Much faster, no hiccups, and works everywhere!


Fallback

Does this only work with a submit button? ie it won't work with type: "button" ?

Fallback

Did you ever figure this out? I'm also not having success with a button that has the 'button' type

Fallback

You won't use it with <input> at all because neither <input type="submit"> nor <input type="button"> allow for HTML inside of it. You'll have to use the <button></button> tag to use this.

Fallback

I am using <button>. Here is the HTML:

<button class="btn btn-primary" data-disable-with="Checking..." data-discount-submit="1" id="job-code-button" type="button">Apply</button>

Fallback

Hmm. I've got this on the login page here on GoRails:

<button class="btn btn-success btn-lg btn-block" data-disable-with="&lt;i class='fa fa-spinner fa-spin'&gt;&lt;/i&gt; Signing in..." name="button" type="submit">Sign in</button>

Only other thing I can think of is maybe your jquery-ujs isn't being loaded successfully and so it never gets triggered on click.

Fallback

Note yours does have type = "submit" whereas @Marklar and I were wondering about buttons with type="button". Do you think that could be the problem? The top 3 lines of my application.js look like this:


//= require jquery
//= require jquery_ujs
//= require jquery-1.11.0.min.js

Fallback

It looks like when I change it to type="button" the form does not submit which means the JS won't fire either.

Fallback

Ahh maybe that's it. My use case is not technically a form. I have a box for entering a promo code within my form but I couldn't make it a form-within-a-form because the submit button would submit the main form. I just have an input with a button that is being listened to for a click event that generates an Ajax request that validates the input. Would that be the problem?

Fallback

Oh I bet that's the case. I would guess that the jquery-ujs script listens to the form's submit event and since you aren't firing it, it won't happen.

It seems you could probably call the jquery-ujs methods to disable the elements in your callback. https://github.com/rails/jq...

Fallback

Sorry if this is a little late but this is how I implemented a button without being type submit:

<%= button_tag "Save", data: {disable_with: " Signing in..."}, :id => "submit-document-button", class: "btn btn-success", type: "button" %>

Fallback

I copied the exact same code but the UJS still isn't working for me. Here is the HTML:


<button class="btn btn-lg btn-success pull-right" data-disable-with="Processing..." id="finish-and-pay-button" name="button" type="button">Finish and Pay</button>

EDIT

I think I finally found a solution that works for my use case. It involves using a link with remote: true and disable_with on the data option. It looks like this:


= link_to "Finish and Pay", pay_job_path(@job), method: :post, id: 'finish-and-pay-button', class: "btn btn-lg btn-success pull-right", remote: true, data: {disable_with: "Processing..."}

Fallback

Great tutorial! Just curious how I could implement this using the 'Font-Awesome-Sass' gem? I tried it, but it's throwing an error because of it having an ERB tag within an ERB tag.

Fallback

What's your code look like?

Fallback

<%= f.button 'Log in', class: 'btn btn-success', data: { disable_with: "<%= icon('spinner') %> Logging In..." } %>

Fallback

You can accomplish that using string interpolation here because it's Ruby code inside the ERB tag.

<%= f.button 'Log in', class: 'btn btn-success', data: { disable_with: "#{icon('spinner')} Logging In..." } %>
Fallback

Doing that does fix the error, but it doesn't show the icon.

Fallback

You'll have to make sure your assets are loaded properly and the icons display elsewhere.

Sometimes it won't display right away because your icon font isn't loaded until you click the button and it takes too long to load.

Fallback

NAILED IT! Thanks Chris!

Fallback

if using font-awesome-rails 4.2.0.0 you might wanna use fa-icon('spinner').
icon('spinner') didn't work for me.

Great tutorial @chris! :)

Fallback

Yup! The libraries have a couple different helper methods. I got confused using the wrong gem the other day. ;)


Fallback

Ok a question here, I used what you taught on a form and it worked perfectly. Now I tried using it on a form that has multiple file fields and it didnt work... using carrierwave.. would there be a specific reason why that wouldnt work?

Fallback

Paste a Github gist of your code and the HTML it generates and I'll take a look for ya!


Fallback

How can we use with f.submit tag

Fallback

Unfortunately you can't because it doesn't allow HTML inside of it.

Fallback

Fallback

This is great. Thanks for the great videos as usual. When using a button that is rending info through ajax, how do you enable the original button? My fa-icon jus keeps spinning. My code: <%= link_to 'Stats', course_heat_map_path(@course, lesson), remote: true, class: 'expandable tiny label', data: {disable_with: " Calculating Stats..."} %> and it is grabbing data from a .js.erb page and rendering it. Thanks in advance

Fallback

You can use Rails UJS directly to do that. I use this and the @form variable is just a jquery selector for the form

$.rails.enableFormElements @form
Fallback

Thank you! just saw you responded


Fallback

The loading animation works except the spinning icon doesn't show up. I'm using Chrome on Windows 7 if that matters. Any help? Thanks!


Fallback

Thank you so much. This is awesome. Been looking for this for ages.
How do I do this for submit_tag? Couldn't get the spinner working. This is what I have right now
<%= submit_tag :Search, class: "btn btn-primary", data: {disable_with: "Searching..."} %>

Fallback

You actually can't do this with submit tags because it generates an input field which doesn't allow HTML in the value. You must use a button, but it renders the same and also still submits the form so basically no difference.

Fallback

Thank you so much Chris for the prompt reply. I don't know how to convert this to f.button

<%= form_tag reply_conversation_path(@conversation), method: :post do %>
<div class="form-group">
<%= text_area_tag 'body', nil, cols: 3, class: 'form-control', placeholder: 'Type your message here', required: true %>
</div>
<%= submit_tag "Send Message", class: 'btn btn-primary', data: {disable_with: "Sending..."} %>
<% end %>

Is it possible to convert this to f.button here?

Fallback

Yep! Just look up the button_tag arguments (can't remember them off the top of my head, but that will show you which arguments you need. It's really simple to migrate it.


Fallback

Im clicking a link in my navbar...If I wanted to get this same function but have the loading indicator display in the middle of the page instead how would I achieve this?

Could you render a partial or do

disable_with: "<div class="loading-indicator" <="" div="">" then css that class ?

Fallback

You probably could actually. You'd need to probably do some hacky things for that CSS, but it could work. However, if you're doing something more complicated than just changing the button text, I might encourage you to just use regular old jQuery to listen to the click or form submit and do the work there. Then you can put your loading-indicator div anywhere you like on the page and your CSS can be relative to its parent div.

Fallback

Awesome, I'll try jquery it to listen for the click on the link, when it does I would then show that div? And that div would then 'disappear' because the new page would have been rendered? Then any css I would just add it to that div class and position it relative to its parent.

I think I got that right? I'm relatively new with using jquery.

Thanks man!

Fallback

Yeah, so by default your CSS for that div probably needs to be "display: none" so it's hidden. Then when they submit the form, you "display: block" or whichever makes the most sense. As long as you're doing a regular form submit, it'll disappear because of the page load.

If you want the form to be an AJAX form, you can simply hide it after the AJAX request gets a response back.

That should do the trick! Plus this will be a good starter project for using jQuery. :)

Fallback

Ah Awesome okay. This is actually for when clicking on a link in my navbar. It delays to show the visiting page because of an api call its making to codeschool.com and teamtreehouse.com so it takes a tad before it returns the results and displaying the page.

This is why I initially wanted to add a processing indicator. BUT... to eliminate all this could I have some sort of like background job that runs once a day to get any updated data and store the results somewhere and then when I render that page I just grab those already retrieved results and display those?

Fallback

Yeah! That would be much better I think. You can write a rake task to do the sync and then use the whenever gem to schedule that to run on a nightly cron. https://gorails.com/episode...

Fallback

You the man Oliver! I got my whenever up (think I have syntax correct) and also my rake task set up.

I'm having a brain fart on how to grab that data it retrieved in that task and place it in one of my controllers which eventually will be used in its view.

Fallback

You'll just need to save those records to the database in the rake task and then load them up in the controller. That should be all you need to do!


Fallback

Anyone seeing this not work in Safari? It works in Crome.

Fallback

I see the same issue. Works great in Chrome and Firefox but no Safari love.


Fallback

Hey Chris, this screencast is an oldie but a goodie. I'm trying to implement this ujs action throughout my new rails app and I'm having success except with the Stripe submit payment button. The button gets disabled through the Stripe javascript call that disables it separately instead of a line of ruby with the button code and I'm unsure as how to incorporate Font Awesome. Have you had any experience integrating this animation on stripe buttons. I have a stackoverflow question addressing it: http://stackoverflow.com/qu...

I'm wondering if I could just eliminate the $('.submit-button').attr("disabled", "disabled"); line

and instead call the disable_with right from the button code.
What do you think?


Login or create an account to join the conversation.