Skip to main content

Developing iPhone applications using Ruby on Rails and Eclipse, Part 3: Developing advanced views for iPhone

Building views and forms

Noel Rappin, Vice President of Rails Development, Pathfinder Development
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.

Summary:  The iPhone and iPod touch made Mobile Safari the most popular mobile browser in the United States. Although Mobile Safari is more than adequate at rendering normal Web pages, many Web developers created versions of applications aimed at the iPhone. Here in Part 3 of this "Developing iPhone applications using Ruby on Rails and Eclipse" series, learn what you should do when the user reaches the end of the list structure and your application actually needs to display some content.

View more content in this series

Date:  15 Jul 2008
Level:  Intermediate
Activity:  3173 views

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
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 %>
        &nbsp;
      <% 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.


Forms

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


Rotation

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.


Final compatibility notes

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, and prompt work just fine in iPhones, but showModalDialog does not. In iUI, the dialog CSS 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.

The future, soon

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.


Resources

Learn

Get products and technologies

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.

About the author

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.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Web development
ArticleID=319731
ArticleTitle=Developing iPhone applications using Ruby on Rails and Eclipse, Part 3: Developing advanced views for iPhone
publish-date=07152008
author1-email=noelrappin@gmail.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers