Skip to main content

56 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

Javascript Frontend


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


Gravatar
Innokenty Longway on

Thank you! Really simple. :)


Gravatar
Jerome . (100 XP) on

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...")}

Gravatar
Chris Oliver (167,500 XP) on

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


Gravatar
Maxime Sahroui on

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

Gravatar
Maxime Sahroui on

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!

Gravatar
Chris Oliver (167,500 XP) on

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?

Gravatar
Maxime Sahroui on

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


Gravatar
Justin Seiter on

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


Gravatar
Dana Nourie (930 XP) on

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


Gravatar
Tomasz Panek on

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="">

Gravatar
Chris Oliver (167,500 XP) on

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..."}

Gravatar
MAteusz on

Nice :)


Gravatar
Mark Radford (1,170 XP) on

Fantastic stuff. Thanks for your efforts.


Gravatar
Bob Wang (10 XP) on

simple and cool.


Gravatar
James M on

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?

Gravatar
Chris Oliver (167,500 XP) on

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

Gravatar
Christopher Drane on

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.

Gravatar
Ben Barber (40 XP) on

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

Gravatar
Ben Barber (40 XP) on

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

Gravatar
James M on

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


Gravatar
Mark Radford (1,170 XP) on

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

Gravatar
David Pell (90 XP) on

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

Gravatar
Chris Oliver (167,500 XP) on

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.

Gravatar
David Pell (90 XP) on

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>

Gravatar
Chris Oliver (167,500 XP) on

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.

Gravatar
David Pell (90 XP) on

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

Gravatar
Chris Oliver (167,500 XP) on

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

Gravatar
David Pell (90 XP) on

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?

Gravatar
Chris Oliver (167,500 XP) on

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...

Gravatar
Mark Radford (1,170 XP) on

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" %>

Gravatar
David Pell (90 XP) on

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..."}

Gravatar
Jordan Godwin (2,680 XP) on

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.

Gravatar
Chris Oliver (167,500 XP) on

What's your code look like?

Gravatar
Jordan Godwin (2,680 XP) on

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

Gravatar
Chris Oliver (167,500 XP) on

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..." } %>
Gravatar
Jordan Godwin (2,680 XP) on

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

Gravatar
Chris Oliver (167,500 XP) on

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.

Gravatar
Jordan Godwin (2,680 XP) on

NAILED IT! Thanks Chris!

Gravatar
Raoul DIFFOUO (10 XP) on

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! :)

Gravatar
Chris Oliver (167,500 XP) on

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


Gravatar
Storm Trooper on

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?

Gravatar
Chris Oliver (167,500 XP) on

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


Gravatar
rajnik on

How can we use with f.submit tag

Gravatar
Chris Oliver (167,500 XP) on

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

Gravatar
rajnik on

Thank you!


Gravatar
Chris Collinsworth (2,400 XP) on

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

Gravatar
Chris Oliver (167,500 XP) on

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
Gravatar
Chris Collinsworth (2,400 XP) on

Thank you! just saw you responded


Gravatar
Jeramae Bohol on

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!


Gravatar
SURESHKUMAR RAMAIAH (50 XP) on

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..."} %>

Gravatar
Chris Oliver (167,500 XP) on

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.

Gravatar
SURESHKUMAR RAMAIAH (50 XP) on

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?

Gravatar
Chris Oliver (167,500 XP) on

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.


Gravatar
Travis Eubanks (60 XP) on

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 ?

Gravatar
Chris Oliver (167,500 XP) on

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.

Gravatar
Travis Eubanks (60 XP) on

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!

Gravatar
Chris Oliver (167,500 XP) on

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. :)

Gravatar
Travis Eubanks (60 XP) on

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?

Gravatar
Chris Oliver (167,500 XP) on

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...

Gravatar
Travis Eubanks (60 XP) on

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.

Gravatar
Chris Oliver (167,500 XP) on

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!


Gravatar
Dan Tappin (860 XP) on

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

Gravatar
JFGrissom on

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


Gravatar
John Viercinski (700 XP) on

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.