Adobe
Products
Acrobat
Creative Cloud
Creative Suite
Digital Marketing Suite
Digital Publishing Suite
Elements
Photoshop
Touch Apps
Student and Teacher Editions
More products
Solutions
Digital marketing
Digital media
Education
Financial services
Government
Web Experience Management
More solutions
Learning Help Downloads Company
Buy
Home use for personal and home office
Education for students, educators, and staff
Business for small and medium businesses
Licensing programs for businesses, schools, and government
Special offers
Search
 
Info Sign in
Welcome,
My cart
My orders My Adobe
My Adobe
My orders
My information
My preferences
My products and services
Sign out
Why sign in? Sign in to manage your account and access trial downloads, product extensions, community areas, and more.
Adobe
Products Sections Buy   Search  
Solutions Company
Help Learning
Sign in Sign out My orders My Adobe
Preorder Estimated Availability Date. Your credit card will not be charged until the product is shipped. Estimated availability date is subject to change. Preorder Estimated Availability Date. Your credit card will not be charged until the product is ready to download. Estimated availability date is subject to change.
Qty:
Purchase requires verification of academic eligibility
Subtotal
Review and Checkout
Adobe Developer Connection / HTML5, CSS3, and JavaScript /

Integrating Rails and jQuery Mobile

by John Bender

John Bender
  • @johnbender
  • johnbender.us

Content

  • Setting up
  • Layout
  • Form validation
  • Data attributes
  • Debugging
  • Where to go from here

Created

21 May 2012

Page tools

Share on Facebook
Share on Twitter
Share on LinkedIn
Bookmark
Print
HTML5 integration JavaScript jQuery mobile Ruby on Rails

Requirements

User level

Intermediate

Sample files

  • johnbender-jqm-rails-50721ed.zip

The mobile web is huge and it's continuing to grow at an impressive rate. Along with the massive growth of the mobile internet comes a striking diversity of devices and browsers. It's not all WebKit, it's definitely not all iOS, and even when it is WebKit there are a vast array of implementation differences between browser APIs. As a result, making your applications cross-platform and mobile-ready is both important and challenging.

jQuery Mobile provides a way to jumpstart development of mobile-friendly applications with semantic markup and the familiarity of jQuery. Rails provides an easy-to-use application environment for serving that markup and managing the data that backs it. By and large they work together flawlessly to create extraordinary mobile experiences, but there are a few integration points that bear highlighting.

In this article, I highlight and then smooth over the rough edges of the integration between these two frameworks. You'll need some basic knowledge of how jQuery Mobile works including an understanding of what data-* attributes are and how they are used to create jQuery Mobile pages, headers, and content blocks. For a quick introduction to the basics there's a great article by Shaun Dunne at ubelly.com that covers everything necessary for this article. Additionally you should be familiar with the basics of building applications in Rails, including form validation flow, templating, layouts, and the asset pipeline.

All the examples and advice in this article are derived from the construction of a sample application that tracks the presence of employees in an office. To access the code for this application, download the sample files for this article or visit https://github.com/johnbender/jqm-rails. It is admittedly a simple application, and complexity often shines a light in little dark corners so if you have suggestions please add them in the comments. Otherwise, the README has detailed setup instructions for the application if you want to play around with it.

Setting up

All jQuery mobile applications expect a certain set of includes in a particular order. When integrating with Rails the default recommendations made in the jQuery Mobile documentation require some slight alterations. The recommended configuration looks something like the following:

<link rel="stylesheet" href="$CDN/jquery.mobile.css"/> <script src="$CDN/jquery-1.6.4.min.js"></script> <script src="$CDN/jquery.mobile.js"></script>

Where $CDN is either your own content delivery network or //code.jquery.com/mobile/$VERSION/ . Rails, as of version 3.1, uses the jquery-rails gem by default in a newly generated application's Gemfile and includes it via the asset pipeline. So your includes will actually take the following form:

<%= stylesheet_link_tag "application" %> <%= javascript_include_tag "application" %> <script src="$CDN/jquery.mobile.css"></script> <script src="$CDN/jquery.mobile.js"></script>

Since the jQuery JavaScript is rolled into the application, include it through the asset pipeline with the following directive:

//= require jquery

At the time of this writing there is one issue with jQuery Mobile 1.1 and jQuery Core 1.7.2— a newly generated Rails Gemfile doesn't have a constraint on the jquery-rails gem version. So in your Gemfile it's a good idea to use gem 'jquery-rails', '=2.0.1' , which carries jQuery Core version 1.7.1 and is compatible with jQuery Mobile 1.1. After that the only thing left is to decide what you want to do with your viewport meta tag. The discussion about device scale and width is a long and complex one. For more details see Peter-Paul Koch's post titled A pixel is not a pixel is not a pixel. The recommended tag for jQuery Mobile applications is:

<meta name="viewport" content="width=device-width, initial-scale=1">

Layout

When building your jQuery Mobile application the navigable views are constructed using data-role=page annotated div elements. Fortunately Rails provides a variety of ways to get your content and visual information into that div , but you'll have to decide which fits your use case best.

The first and least complicated involves simply rendering all your view content into a data-role=page div element in the top level application layout (see app/views/layout/application.html.erb in the sample files):

<body> <div data-role="page"> <div data-role="header"> <h1><%= yield :heading %></h1> </div> <div data-role="content"> <%= yield %> </div> </div> </body>

Here the main content of a view will be rendered into the yield call and a content_for block can be used to push bits of content elsewhere. A simplified version of the users index at app/views/users/index.html.erb view would look something like the following:

<% content_for :heading do %> All users <% end %> <ul data-role="listview" data-filter="true"> <% @users.each do |user| %> <li><%= user.email %> - <%= user.status %></li> <% end %> </ul>

This at least reduces the burden on the view itself leaving a large chunk of the work in keeping the pages consistent to the layout. As the complexity of your Rails application grows it's likely that the views will need to make more detailed alterations to their parent layouts that don't make sense as content_for yields. One approach is to create a jQuery Mobile page partial and use a render block (see app/views/shared/_page.html.erb); for example:

<div data-role="page"> <div data-role="header"> <h1><%= h1 %></h1> </div> <div data-role="content"> <%= yield %> </div> </div>

Using it in a view would take the following form (see app/views/shared/sample.html.erb):

<% render :layout => 'shared/page', :locals => { :h1 => "foo" } do %> <div>The Content</div> <% end %>

This has the advantage of pushing the control down into the views a bit more and making the page configuration requirements more explicit by requiring the user to provide the :locals values. As you'll see, being able to tightly control the configuration of the page elements with data attribute values is important.

Form validation

jQuery Mobile's support for caching multiple pages in an HTML document can cause issues for Rails form validation, as well as any sequence of actions that result in navigating to the same URL many times in a row. By default, the pages that exist in the HTML document will be removed when navigating away from them but in general the framework tries to source content and views locally where possible. The following simple example illustrates this scenario:

<div data-role="page" data-url="/foos"> <div data-role="content"> <a href="/bars">Go to Bars</a> All the Foos </div> </div> <div data-role="page" data-url="/bars"> <div data-role="content"> <a href="/bars">Go to Bars</a> All the Bars </div> </div>

Assuming the first page is the current active page and DOM caching is turned on, clicking one of the /bars links will navigate to the page that already exists in the DOM from that URL (the data-url is added by the framework to identify from where the content came). As a consequence, clicking the /bars link on the /bars page is effectively a no-op. This is important in Rails because invalid form submissions render the new view consistently under the index path (see app/controllers/users_controller.rb).

def create @user = User.new(params[:user]) if @user.save redirect_to root_url else # /users == /users/new render :new end end

On validation failure, the content of /users is effectively identical to /users/new , save for the possible addition of the error message markup. The problem is that the page content for /users also has a form that submits to /users as its action, which is the aforementioned no-op.

The solution I normally recommend is to add data-ajax=false on the form, which will prevent the framework from hijacking the submit. Unfortunately that also means it won't pull the content and apply an animation/transition. One quick way to get around the problem and retain the nice transitions is to differentiate the action path using a URL parameter with a helper (see app/helpers/application_helper.rb).

# NOTE severely pushing the "clever" envelope here def differentiate_path(path, *args) attempt = request.parameters["attempt"].to_i + 1 args.unshift(path).push(:attempt => attempt) send(*args) end

As noted in the comment, this is probably a bit too clever (pejorative form), but it handles differentiating parameterized or unparameterized Rails paths and URL helpers by adding an attempt query parameter. In use as the :url hash parameter to the form_for and form_tag helpers it looks like the following:

# new form :url => differentiate_path(:users_path) # edit form :url => differentiate_path(:user_path, @user)

For each new submission it will increment the parameter value and signal to jQuery Mobile that the path and the content are different. In addition you will want to annotate your form page with data-dom-cache=true so that it preserves the previous form submission page contents for a sane back button experience (easier with the _page partial). Otherwise jQuery Mobile will reap the previous form validation failure pages from the DOM and try to reload the requested URL in the history stack. If that happens to be /users?attempt=3 the content won't be the submission form but rather a list of the users or something else if that URL requires validation. By preserving the pages, the back button will simply let users traverse backwards through their submission failures.

Data attributes

jQuery Mobile makes heavy use of data attributes for annotating DOM elements and configuring how the library will operate. During beta we came to the consensus that data attribute use was becoming more and more common and decided that a namespacing option would be valuable. Rails also makes fairly heavy use of data attributes for its unobtrusive JavaScript helpers, though it doesn't appear from a simple grep data- jquery_ujs.js that there are any conflicts. If that changes you can alter jQuery Mobile's data attribute namespace with a simple addition to app/assets/javascripts/application.html.erb:

//= require jquery //= require jquery_ujs //= require . $( document ).on( "mobileinit", function() { $.mobile.ns = "jqm-"; });

The mobileinit event fires before jQuery Mobile has enhanced the DOM and is generally where you configure the framework with JavaScript. As a result it's important that the binding comes after the inclusion of jQuery in the asset pipeline and before jQuery Mobile is included either in the pipeline or in the head of your document. With the above snippet in place the data attributes in the page partial would change to the following:

<div data-jqm-role="page"> <div data-jqm-role="header"> <h1><%= h1 %></h1> </div> <div data-jqm-role="content"> <%= yield %> </div> </div>

If you are beginning a new application and you plan to use libraries that rely on data attributes it might be better to start out with a namespace since changing it after the fact can be time consuming and error prone.

Debugging

Tooling for mobile web development is still evolving, and though Weinre and Adobe Shadow present intersecting opportunities to debug CSS, markup, and JavaScript you can still expect server-side errors. jQuery Mobile, being unaware of the environment in which it's working, must report a server error in a user friendly fashion. As a result it swallows the Rails stack traces you've come to know and love and just displays an error alert. By binding the special pageloadfailed event you can replace the DOM content with the stack trace when one occurs (see app/assets/javascripts/debug/pagefailed.js.erb).

function onLoadFailed( event, data ) { var text = data.xhr.responseText, newHtml = text.split( /<\/?html[^>]*>/gmi )[1]; $( "html" ).html( newHtml ); } $( document ).on( "pageloadfailed", onLoadFailed);

To make sure that it only loads in development you can wrap that in a <%= if Rails.env.development? %> block and the asset pipeline will render the erb without the snippet in production or test environments.

Note: I'd like to thank some helpful attendees at my RailsConf talk who informed me about using erb in the asset pipeline! If that's you please contact me on twitter or GitHub.

Where to go from here

If you're interested in taking this a bit further, jQuery defines its constituent modules using Asynchronous Module Definition (AMD), so integrating require.js into the asset pipeline and defining a meta module for just the parts you want is one way to reduce the wire weight of the include. Also it's worth examining WURFL integration through the gem of the same name if you are creating a mobile version of an existing website and you want to redirect users properly. Otherwise, Rails and jQuery Mobile form an exceptionally productive combination for building mobile web applications.

If you find errors in the sample application please fork the sample application repository, make the alteration to doc/post.md, and submit a pull request.

More Like This

  • Introducing the HTML5 storage APIs
  • Introducing theexpressiveweb.com beta
  • Adobe, standards, and HTML5
  • Developing HTML5 games with Impact JavaScript game engine and Dreamweaver CS5.5
  • Using the Geolocation API
  • CSS3 basics
  • Real-time data exchange in HTML5 with WebSockets
  • Backbone.js Wine Cellar tutorial – Part 2: CRUD
  • JavaScript object creation: Learning to live without "new"
  • Backbone.js Wine Cellar tutorial – Part 1: Getting started

Products

  • Acrobat
  • Creative Cloud
  • Creative Suite
  • Digital Marketing Suite
  • Digital Publishing Suite
  • Elements
  • Mobile Apps
  • Photoshop
  • Touch Apps
  • Student and Teacher Editions

Solutions

  • Digital marketing
  • Digital media
  • Web Experience Management

Industries

  • Education
  • Financial services
  • Government

Help

  • Product help centers
  • Orders and returns
  • Downloading and installing
  • My Adobe

Learning

  • Adobe Developer Connection
  • Adobe TV
  • Training and certification
  • Forums
  • Design Center

Ways to buy

  • For personal and home office
  • For students, educators, and staff
  • For small and medium businesses
  • For businesses, schools, and government
  • Special offers

Downloads

  • Adobe Reader
  • Adobe Flash Player
  • Adobe AIR
  • Adobe Shockwave Player

Company

  • News room
  • Partner programs
  • Corporate social responsibility
  • Career opportunities
  • Investor Relations
  • Events
  • Legal
  • Security
  • Contact Adobe
Choose your region United States (Change)
Choose your region Close

North America

Europe, Middle East and Africa

Asia Pacific

  • Canada - English
  • Canada - Français
  • Latinoamérica
  • México
  • United States

South America

  • Brasil
  • Africa - English
  • Österreich - Deutsch
  • Belgium - English
  • Belgique - Français
  • België - Nederlands
  • България
  • Hrvatska
  • Česká republika
  • Danmark
  • Eastern Europe - English
  • Eesti
  • Suomi
  • France
  • Deutschland
  • Magyarország
  • Ireland
  • Israel - English
  • ישראל - עברית
  • Italia
  • Latvija
  • Lietuva
  • Luxembourg - Deutsch
  • Luxembourg - English
  • Luxembourg - Français
  • الشرق الأوسط وشمال أفريقيا - اللغة العربية
  • Middle East and North Africa - English
  • Moyen-Orient et Afrique du Nord - Français
  • Nederland
  • Norge
  • Polska
  • Portugal
  • România
  • Россия
  • Srbija
  • Slovensko
  • Slovenija
  • España
  • Sverige
  • Schweiz - Deutsch
  • Suisse - Français
  • Svizzera - Italiano
  • Türkiye
  • Україна
  • United Kingdom
  • Australia
  • 中国
  • 中國香港特別行政區
  • Hong Kong S.A.R. of China
  • India - English
  • 日本
  • 한국
  • New Zealand
  • 台灣

Southeast Asia

  • Includes Indonesia, Malaysia, Philippines, Singapore, Thailand, and Vietnam - English

Copyright © 2012 Adobe Systems Incorporated. All rights reserved.

Terms of Use | Privacy Policy and Cookies (Updated)

Ad Choices

Reviewed by TRUSTe: site privacy statement