The first two parts of this series cover two important aspects of structuring a Ruby on
Rails application to serve special content to users of the Mobile Safari browser found
in the iPhone and iPod touch. Part 1 discusses how to set up your server to
detect and serve alternate content to Mobile Safari. The mechanism chosen, which is not
the only possible way to manage this, involves creating a pseudo-MIME type, matching the
user-agent string and using Rails' respond_to mechanism.
Part 2 explores the actual content you might create for an iPhone or iPod touch. The iUI library was used as a mechanism for keeping a Web application within Apple's look-and-feel guidelines. The resulting applications look much like native iPhone applications. That article goes as far as creating list structures for drill-down navigation, similar to the iPhone's native Mail application. Such structures are recommended by Apple as being easy to navigate and small in size, allowing for a quick download, even over a slower Edge network connection.
This final series article looks at what you should do when the user reaches the end of the list structure and your application actually needs to display some content. iUI provides some useful features for content and form layout. It also covers some features that will give your application some extra polish, such as capturing the user's rotation of the phone and adding an icon to display on the iPhone's home screen.
To run the examples, you need a few tools. You need Ruby and Rails, of course. An editor or integrated development environment (IDE) that works with Rails, such as Eclipse with Aptana Studio, is helpful. A simulator for the iPhone view screen is also helpful (Part 1 discusses the benefits of a few options). The example used here is Soups OnLine, a recipe-trading site originally created for my book Professional Ruby on Rails. The exact details of that site are not important for this article, however. The iUI toolkit provides iPhone look and feel with Cascading Style Sheets (CSS) and JavaScript. The rails_iui plug-in was created to wrap iUI features into Rails helpers and other best-practice Rails idioms.
Breaking out of the list with a panel
When I left off in Part 2, I had created a navigation structure that presented the iPhone- or iPod-touch user with a list of navigation options. Some options led to a second list of recipes organized alphabetically or by most recent entry. Clicking on a recipe in one of t hose listings would presumably take the user to a page describing the individual recipe.
When designing the individual elements on the iPhone display page, it's important to keep the particulars of the iPhone screen in mind. The viewport settings I chose for this application set the width of the page to the width of the device's current orientation. Since I want this Web application to look like a native iPhone application, I also disabled the user's ability to zoom further in. As a result, it's important that my design keep within the 320-pixel width of an iPhone in vertical orientation. (I'll talk about orientation changes in a bit.) If the page can be kept within one 320x480-pixel screen, that's ideal. However, if the user has to scroll in one direction, that's OK.
Other aspects of the iPhone display compound the effect of the small screen size. The iPhone has a much-denser pixel arrangement than a normal desktop screen, meaning that fonts and images appear smaller on the actual phone than on a simulated screen. You may have to increase font sizes for legibility. Furthermore, the user's finger on the screen is not as precise as a mouse tap. Apple recommends that clickable targets be at least 44 pixels square for best usability.
With that in mind, Figure 1 shows what I wound up with for a recipe display page. I kept the styles to the ones provided by iUI to keep things a bit simpler.
Figure 1. Recipe display page
The Rails code for this page takes advantage of some rails_iui helpers to simplify
access to iUI features. This file is app/views/recipes/show.iphone.erb. The only
controller change needed to enable this is to add a format.iphone line to the relevant controller action's respond_to block, shown below.
Listing 1. Recipe display code
<% panel do %>
<% if @recipe.has_image? %>
<%= image_tag(@recipe.soup_image.public_filename, :align => :left,
:height => 80, :width => 80,
:style => "padding: 5px" ) %>
<% else %>
<% end %>
<div>Servings: <%= @recipe.servings %></div>
<br/>
<div>Description:</div>
<div><%= @recipe.description %></div><br/>
<div>Ingredients:</div>
<% fieldset do %>
<% for ingredient in @recipe.ingredients %>
<% row_label do %>
<%= h ingredient.display_string %>
<% end %>
<% end %>
<% end %>
<div>Directions:</div>
<% fieldset do %>
<%= row @recipe.directions %>
<% end %>
<div>Tags: <%= h @recipe.tag_list.to_s %></div>
<% end %>
|
The iUI CSS structure, as it is at this writing, has a couple of quirks, mostly having
to do with limitations on where particular classes have to occur with respect to other
tags. In any case, this code makes use of a few rails_iui block helpers that wrap div tags with particular CSS classes defined by iUI.
The first of these is the handler panel at the top of the
code segment, which is a wrapper for a div tag with the
class panel. The panel class sets
the element's border to the size of the device box, adds 10 pixels of padding, and puts
the background color and pinstripes on the page. This mimics the iPhone settings page, among other pages.
The other distinctive feature of this screen is the rounded rectangles. Within Mobile
Safari, this is easily managed with a custom CSS property called -webkit-border-radius, which, in this case, is set to 10px. Within iUI panels, the fieldset tag
is used to specify the boundaries of a rounded rectangle. In addition to the border
radius, the panel > fieldset selector also sets a top margin, a white background,
the border, and 16-point right-aligned text (as you'd see in an individual element of
the iPhone settings page). The fieldset block helper is
defined by rails_iui to place a fieldset tag pair.
Now, I said that the text in a fieldset is right-aligned
when anybody can see that the text in Figure 1 is left-aligned. That's because there
are further CSS classes that change the text alignment. The row class, seen in the ingredient listing, sets a 42-pixel high
block, suitable for a single line in a stack of lines inside a rounded rectangle.
Inside a row class, the label tag
sets the text to bold and puts it back to the end of the line. The native version of
this is visible in the settings page, where a label is left-aligned and the setting toggle is on the right.
The rails_iui defines two helpers for rows, both of which are used in this example for
demonstration purposes. The row version takes one argument,
a string, and an optional block. The string is used as the label text and the block is
the row content. (As with all Rails block helpers, the nonblock version is delimited by
ERb template's output pair <%=, while the block version
uses just %lt;%). The row_label
helper takes a block and places the block text in the label tag, with no other row contents.
Mobile Safari has a number of features designed to overcome the potential limitations
of entering form data on a tiny touch screen. The most obvious of these features
—the software keyboard for text input and the large scroll for select lists — are automatically available as an iPhone Web
developer. However, these elements take up screen space when invoked, so remember that
your users will not be able to see very much of the page beyond the field and the Mobile Safari input panel.
For text fields, Mobile Safari defines two custom attributes that control the behavior
of software keyboard: autocorrect and autocapitalize. Both are on by default, but can be set to off to remove the feature. Auto-correct controls whether your user
will see suggestions for common misspellings displayed below the input field.
Auto-capitalize starts a new sentence with a capital letter. You can control either
with Rails by passing the attribute as part of the HTML options of the text or password
tags: :autocorrect => "off". Turn them off if there is a
strong chance that the user will be typing text that is not a word or sentence, such as a login or password field.
Here's the search form I put together for Soups OnLine. As specified in Part 1, the Search button in the toolbar
is drawn by the rails_iui toolbar helper method <%= iui_toolbar
"Soups OnLine", new_search_url %>.
I did make one minor change in the helper method from what is shown in Part 1: the button link was changed to a target
of self, causing the entire screen to redraw, rather than
doing the swipe in behavior of the list navigation. I made this change on the theory
that the search is not really part of the list navigation and should behave slightly
differently. The toolbar method now looks like Listing 2.
Listing 2. iui_toolbar helper method
def iui_toolbar(initial_caption, search_url = nil)
back_button = button_link_to("", "#", :id => "backButton")
header = content_tag(:h1, initial_caption, :id => "header_text")
search_link = if search_url
then button_link_to("Search", search_url, :id => "searchButton",
:target => "_self")
else ""
end
content = [back_button, header, search_link].join("\n")
content_tag(:div, content, :class => "toolbar")
end
|
The search screen I built is on the simple side, again using a minimal feature set to start with on the smaller screen.
Figure 2. Search display
The form consists of a text-entry field, a select pull-down, and a toggle switch, which
is built by iUI to mimic the native iPhone toggle control. To make this form work, I
start with the controller. The SearchController class
already existed in the Soups OnLine application to create a desktop search form.
Modifying the new controller action is straightforward.
Listing 3.
SearchController action
def new
@search = Search.new
@tag_cloud = TagCloud.calculate(Recipe)
@tag_counts = TagCloud.tag_counts(Recipe)
@tags = @tag_cloud.keys.sort
respond_to do |format|
format.html
format.iphone
end
end
|
The Search object is a small little model object designed to
allow the Rails view to use form-builder methods that require the form to map to a
Rails model object. When I say small, I mean it — right now, the class is just a list of accessors:
class Search
attr_accessor :keyword, :tags, :ingredients
end
|
Later, this class would be a great place to put the actual search logic to keep the Recipe class from getting clogged up.
The TagCloud lines are used to create a list of the
available tags for placement in the pull-down list. The code uses the
acts_as_taggable_on_steroids plug-in, the details of which are irrelevant here. The
relevant point is that @tags will contain a list of strings.
Finally, the respond_to block handles the iPhone requests.
Notice that whereas the list actions in part two of this article were careful to
specify :layout => false, this controller does not. The
difference is the way the actions are invoked. The list actions were invoked via the
default iUI Asynchronous JavaScript + XML (Ajax) action, meaning they were replacing an
existing element, and therefore, did not need to redraw the entire layout. As shown, the
Search button is invoked with a target of _self,
which iUI interprets as a regular HTML link, redrawing the entire screen — meaning that the layout needs to be drawn.
The view screen that draws this is mostly a typical Rails form, as shown below.
Listing 4. View code for search form
<%= iui_toolbar "Soups OnLine", new_search_url %>
<div selected="true">
<% form_for @search do |f| %>
<table>
<tr>
<th>Keyword:</th>
<td><%= f.text_field :keyword %></td>
</tr>
<tr>
<th>Tag</th>
<td><%= f.select :tags, @tags,
:include_blank => true %></td>
</tr>
<tr>
<th>Ingredients?</th>
<td>
<%= f.toggle(:ingredients) %>
</td>
</tr>
</table>
<%= f.submit "Search" %>
<% end %>
</div>
|
The first three-quarters of this are a lock-standard Rails form. The view starts by redrawing the toolbar because the view is responsible for the entire page. Then there's a normal form_for and the text field and pulldown tags are also standard. The toggle call is a rails_iui helper that uses iUI-provided toggle classes. The HTML resulting from the helper looks like Listing 5:
Listing 5. HTML for toggle control
<input id="search_ingredients" name="search[ingredients]"
type="hidden" value="OFF" />
<div class="row">
<div class="toggle" id="search_ingredients_toggle"
onclick="$('search_ingredients').value =
($('search_ingredients').value == 'OFF') ? 'ON' : 'OFF';"
toggled="OFF">
<span class="thumb"></span>
<span class="toggleOn">ON</span>
<span class="toggleOff">OFF</span>
</div>
</div>
|
The iUI toolkit provides the CSS classes for toggle, thumb, toggleOn, and toggleOff, which draw the toggle control, as well as some JavaScript
to change the control switch when it's clicked. What iUI doesn't do, however, is tie
the toggle switch to a form control, so rails_iui steps in here. First, the helper puts
in a hidden field with the initial value of the toggle. That's the value that will be
sent back in the form. The rails_iui helper also adds the JavaScript onclick event handler for the toggle div.
That handler changes the value of the hidden field back and forth, based on the opposite
of the current value. If rails_iui is loaded, the toggle
helper is available to any form_for block.
To make the Submit button itself a little snazzier, iUI provides the whiteButton, blueButton, and grayButton CSS classes, any of which will make the button somewhat larger and more iPhone-like.
One of the most distinctive features of the Mobile Safari browser is the fact that the user can turn it 90 degrees to use the browser in landscape or portrait orientations. While the browser itself does a nice job of redrawing the site to adapt to the different widths, you might want to give yourself more control over your site's response to a change in direction. The combination of iUI and rails_iui gives you two ways to specify orientation behavior.
The iUI toolkit tracks orientation changes and changes the document.body.orient property between the values profile and landscape. You can then use
those values to specify CSS behavior based on the value of that property, as in the
line in Listing 6, which changes the margin and width of an h1 tag if the device is in landscape mode.
Listing 6. Sample CSS change based on orientation
body[orient="landscape"] > .toolbar > h1 {
margin-left: -125px;
width: 250px;
}
|
For more elaborate control over the orientation-change behavior, the rails_iui plug-in
allows you to define a callback that sends an Ajax request back to your Rails
application when an orientation change is detected. Mobile Safari defines an onorientationchange event handler and a window.orientation property to handle the orientation events. Rails can create an
Ajax observer for this event and make a remote call back to the server.
To use this callback, change your layout to look like Listing 7.
Listing 7. Layout with orientation-change callback
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Recipes: <%= controller.action_name %></title>
<meta name = "viewport"
content = "width = device-width, user-scalable=no">
<%= stylesheet_link_tag 'iphone' %>
<%= include_iui_files %>
<%= javascript_include_tag :defaults %>
<%= observe_orientation_change :controller => 'browsers',
:action => :orientation_change %>
</head>
<body <%= register_orientation_change %>>
<%= yield %>
</body>
</html>
|
There are three changes between this layout file and the one created in Part 1 of this
series. First, the default JavaScript libraries are included — prototype is
needed for the Ajax callback. The other two changes are the observe_orientation_change and register_orientation_change rails_iui helper methods. The register method is the
simpler of the two. All it does is output a string into the body tag that sets the
event handler, as shown below.
Listing 8. Register orientation-change helper
def register_orientation_change
'onorientationchange="updateOrientation();"'
end
|
The observe method takes anything that can be passed to the :url option of a remote helper as its argument and creates the updateOrientation() method alluded to by the register helper.
Listing 9. Observe orientation-change helper
def observe_orientation_change(url_options = {})
remote = remote_function :url => url_options,
:with => "'position=' + String(window.orientation)"
func = "function() { #{remote}; };"
javascript_tag("function updateOrientation() { #{remote}; }")
end
|
This method uses the Rails standard remote_function helper
to create a JavaScript callback using the URL information passed to the helper and
outputs the function inside a script tag. The call back to
the server contains one parameter: position. The value of the
variable is "0" if the device is in the normal upright
portrait position, "90" if the phone has been turned counter
clockwise, and "-90" if the phone has been turned
clockwise. (The upside-down position is not currently recognized by the device, but
would be "180" if that rotation is ever supported in the
future.) From the callback, you can do anything that a Rails RJS JavaScript template can
do, including changing any Document Object Model (DOM) object on the screen.
With these two mechanisms, it's easy for you to react to users changing the orientation of their browser.
This series should give you a great start on creating your iPhone-specific Web application. Here are a few more things to keep in mind:
- The iPhone automatically detects things that look like phone numbers and allows users
to tap on them to call using the phone. (The iPod does not perform this detection.) You
can turn off this feature on your page with the
<meta name = "format-detection" content = "telephone=no">metatag. You can then explicitly identify phone numbers by turning them into HTML links of the form<a href="tel:555-1234">555-1234</a>. - Links to Google Maps pages exit Mobile Safari and open the Maps application. Similarly, links to YouTube pages open the YouTube application.
- The JavaScript functions
alert,confirm, andpromptwork just fine in iPhones, butshowModalDialogdoes not. In iUI, thedialogCSS class mimics some dialog behavior, acting as an overlay over the top of the screen. - Flash, Java™ applications, Wireless Markup Language (WML), Scalable Vector Graphics (SVG), and Extensible Stylesheet Language Transformation (XSLT) do not work in the Mobile Safari Browser at this time. File uploads and downloads are not supported, although that may change in the 2.0 version of the iPhone firmware. Mouse-over events, tooltips, and hover styles also don't work.
- Graphics Interchange Format (GIF), Portable Network Graphics (PNG), and Tagged Image File Format (TIFF) images must be 2 megapixels or less when decoded. JPEG images larger than 2 megapixels are subsampled, up to a limit of 32 megapixels. Individual text or media files must be less than 10 MB.
- The iPhone and iPod touch allow users to place icons representing a specific Web applications on their home screens. To give your application a custom icon, place a PNG file at /apple-touch-icon.png. The icon should be 57 pixels square with square corners. Do not try to include the gloss that the native icons have. The iPhone- or iPod-touch operating system will automatically round the corners and add the glossy effect.
It's been an exciting year for the iPhone and iPod touch. Even as the 2.0 version adds third-party native applications, the need for iPhone-enabled Web applications will only grow. The iUI toolkit and the rails_iui plug-in will continue to make it easy for any developer to make their Mobile Safari application look great.
Learn
-
Read
Professional Ruby
on Rails
to learn how to take a beginner Web site and make it great.
-
See Plugging
Aptana into an existing Eclipse configuration for instructions for integrating Aptana Studio into Eclipse.
-
"iPhone
on Rails -- Creating an iPhone optimised version of your Rails site using iUI and Rails
2" is a blog article about using iPhone and Rails.
-
Visit the Apple Developer Connection
for more resources for iPhone Web applications (registration required).
-
Start this series with "Developing iPhone applications using Ruby on Rails and Eclipse, Part 1" to learn
how to detect Mobile Safari from a Ruby on Rails application. Then check out Part 2 for more.
-
Check out the iUI toolkit.
- Explore iPhoney.
-
Check out the "Recommended Eclipse reading list."
-
Browse all the Eclipse content on developerWorks.
-
New to Eclipse? Read the developerWorks article "Get started with Eclipse Platform" to learn its origin and architecture, and how to extend Eclipse with plug-ins.
-
Expand your Eclipse skills by checking out IBM developerWorks' Eclipse project resources.
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
Stay current with developerWorks' Technical events and webcasts.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
Get products and technologies
-
Install the rails_iui
plug-in at git://github.com/noelrappin/rails-iui.git.
-
Visit Aptana.com to download Aptana Studio.
-
Go to the Aptana Update Site to obtain the Aptana RadRails and iPhone plug-ins.
-
Check out the latest Eclipse technology downloads at IBM alphaWorks.
-
Download Eclipse Platform and other projects from the Eclipse Foundation.
-
Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
Discuss
-
The Eclipse Platform newsgroups should be your first stop to discuss questions regarding Eclipse. (Selecting this will launch your default Usenet news reader application and open eclipse.platform.)
-
The Eclipse newsgroups has many resources for people interested in using and extending Eclipse.
-
Participate in developerWorks blogs and get involved in the developerWorks community.
Noel Rappin is the vice president of the Rails practice at Pathfinder Development (http://www.pathf.com), and has a decade of experience with web application development. He has a doctorate from the Georgia Institute of Technology, where he studied how to teach Object-Oriented design concepts. He is the author of Professional Ruby on Rails, and the co-author of wxPython in Action and Jython Essentials. Read more at http://www.pathf.com/blogs and http://10printhello.blogspot.com.





