Screencasts

Graceful Degradation with jQuery and Rails 3

Show Notes

Overview

Throughout your web development career you may be required to develop sites that can function when JavaScript is switched off. This may be required because of security or accessibility policies, or perhaps just plain old ideology.

Graceful Degradation is a term used to describe designing websites so that they still function as well as possible in older browsers, or browsers with less functionality.

In this video tutorial we’ll look at how to “gracefully degrade” the behavior of our Rails 3 app that uses jQuery, when it encounters an environment that has JavaScript switched off.

What You'll Learn:

  • How to make the destroy links degrade gracefully
  • Write jQuery to restore original Rails behaviour

Follow Along

rails new widget_manager -m http://screencasts.org/templates/graceful-degradation-template.rb

Links

Script

Today we’re going to talk about how to apply Graceful Degradation with jQuery in Rails 3.

We’re going to assume you have a bit of experience with JavaScript and jQuery. If you’re new to jQuery, check out our Introduction to jQuery screencast before starting this video.

We’re also going to assume you have some basic experience with Rails, and know how to install it. We’ll assume you know how to navigate through the rails project folders, and understand the basic relationships between files.

Graceful Degradation is a term used to describe designing websites so that they still function as well as possible in older browsers, or browsers with less functionality.

For example, we’d want our super-awesome website with tons of jQuery behavior to at least read and function correctly on a text-only browser.

What we’ll do today

So today we’ll look at how to “gracefully degrade” the behavior of our Rails 3 app that uses jQuery, when it encounters an environment that has JavaScript switched off.

Throughout your web development career you may be required to develop sites that can function when JavaScript is switched off. This may be required because of security or accessibility policies, or perhaps just plain old ideology.

In the days before Rails 3, the JavaScript helpers included with previous versions of Rails weren’t keeping up with the web’s best practices of unobtrusive JavaScript.

Take, for example, when you wanted to do a destroy link: The code that Rails generated had inline Javascript. If you disabled JavaScript, you’d see that Rails incorrectly navigated to the show path, when it should have been going to the destroy path. It would do this because Rails relied on its inline JavaScript to run the destroy action.

<a href="/widgets/1" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);var s = document.createElement('input'); s.setAttribute('type', 'hidden'); s.setAttribute('name', 'authenticity_token'); s.setAttribute('value', 'dOy7LvaWLX8IAYUQ3s8SOQcLFoQ6kYpZNJu1RpQ+Fro='); f.appendChild(s);f.submit(); };return false;">Destroy</a>

Let’s look at what Rails does now. Rails 3 has made leaps and bounds in adopting today’s best practices, and is also JavaScript framework agnostic. Included with every Rails project is a rails.js file which is an unobtrusive JavaScript driver for Rails’ default JavaScript framework, Prototype. If you want to use another framework, you have to write or supply your own driver. Fortunately, there is a jQuery driver already written for you, and it’s really easy to include it. Check the show notes under this video to see how it’s done.

While Rails is doing a great job bringing these improvements and javascript-agnosticism, there’s still an issue with the default destroy links. If you disable JavaScript and click a destroy link, you still see that it incorrectly goes through to the show action.

rails new widget_manager -m http://screencasts.org/templates/graceful-degradation-template.rb

If you copy the template line from the show notes, it will set up your Rails 3 application with jQuery and the jQuery driver. It will also run the scaffold generator to create a Widget controller, model and views. A Widget is just something we made up for this screencast example, and the only attribute it has is a name.

Modifying our Rails app

So let’s modify our Rails app to correctly perform it’s destroy action when JavaScript is turned off. This is what graceful degradation is all about. To do this, we need a web form that will do the exact same thing that the Rails driver would do. So let’s create a delete action in our widgets controller.

class WidgetsController < ApplicationController
  
  def delete
    @widget = Widget.find(params[:id])
  end
  ...
end

Once we’ve done that, we can then create the corresponding erb file, delete.html.erb.

touch /views/widgets/delete.html.erb

Next, let’s wire up the routes file.

resources :widgets do
    get :delete, :on => :member
end

Now lets run rake routes from the command line to make sure we’re we have it setup correctly.

delete_widget GET    /widgets/:id/delete(.:format) {:action=>"delete", :controller=>"widgets"}

As you can see, we have a delete_widget_path now. So lets modify our widgets’ index.html.erb file and point the destroy link to our new route.

<%= link_to 'Destroy', delete_widget_path(widget) %>

Lets add a widget and then try the new “Destroy” link. Now, when we click through, it goes to a blank page.

So lets add a form...

<%= form_for(@widget, :html => {:method => :delete}) do |f| %>
    <h1>Are you sure you want to delete the widget "<%= @widget.name %>"?</h1>
    <p>
        <%= f.submit "Delete Widget" %>
        <%= link_to 'Back', widgets_path %>
    </p>
<% end %>

We need to specify the method as :delete so that Rails knows to route the request to the destroy action.

DELETE /widgets/:id(.:format)        {:action=>"destroy", :controller=>"widgets"}

When we click on the Delete Widget button, our “Awesome Widget” is gone...

So we’ve now successfully setup our app’s behavior when JavaScript is switched off.

Now let’s go ahead and add the jQuery behavior...

In our application.js file, let’s add a selector for all links that end with the string /delete

We now want to remove /delete from the href attribute. Next, we add the “delete” string to the data-method attribute. Finally, we add a confirmation message, such as "Are you sure?" to be shown to the user.

$(function(){

  $('a[href$="/delete"]').each(function(){
    this.href = this.href.replace(/\/delete$/, "");
    $(this).attr("data-method","delete").attr("data-confirm","Are you sure?");
  });
  
});

What we’ve just done is write code in jQuery which behaves just like the code Rails originally created with the scaffolding generator.

<%= link_to 'Destroy', widget, :confirm => 'Are you sure?', :method => :delete %>

So now this will be the code that runs when JavaScript is enabled, and when JavaScript is disabled, it will default to the web form behavior that we setup earlier.

That’s it

And that’s it! We don’t need to add anything else. We don’t need to write JavaScript in order to deal with sending a DELETE request to the destroy action. This is because the jQuery driver code, found in rails.js, uses the live() jQuery method when applying behavior to the delete links.

Normally when click() method or bind() method code is executed, it gets attached to the elements in the DOM that are present at that time. Any elements added to the DOM after the code is executed won’t have the event listeners bound to them.

To get around this, the live() method is used to bind event listeners on selectors in the DOM that are there initially, and then when new elements of the same selector are introduced later. This is known as Event Delegation.

So when we add data-method and data-confirm attributes to the links, the selectors are there for jQuery to use. jQuery recognizes these attributes and creates the destroy form without us doing any extra work.

...
$(this).attr("data-method","delete").attr("data-confirm", "Are you sure?");
...

OK - let’s try it out... We’ll add another widget... Click Destroy... We see a JavaScript confirm window... Click OK... and as you can see it gets deleted...

And that’s how you make your Rails 3 projects degrade gracefully!

Thanks for watching! Subscribe to our RSS feed, follow us on Twitter, and please leave any questions, comments or suggestions for new screencasts in the comments below. If you like our videos, please like us on Facebook, and feel free to join the conversation there. See you next time!

← Latest Episodes

blog comments powered by Disqus