Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Developing iPhone applications using Ruby on Rails and Eclipse, Part 2: Displaying iPhone content to the client

Using iUI and the iPhone list structure

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 2 of this "Developing iPhone applications using Ruby on Rails and Eclipse" series, we learn the common use of drill-down lists as a navigation method

View more content in this series

Date:  08 Jul 2008
Level:  Intermediate
Also available in:   Russian  Japanese

Activity:  36861 views
Comments:  

Part 1 of this series took an existing Ruby on Rails Web application and began the process of augmenting it to serve iPhone users. That article concentrates on the support needed on the server side to allow different content to be sent to an iPhone user, as well as allowing that user to opt out and see the full site display. Parts 2 and 3 focus on the actual content being sent to the user and how to make that content match the expectations of an iPhone or iPod touch user. Part 2 focuses on the common use of drill-down lists as a navigation method, and Part 3 focuses on forms, groupings, and other more advanced topics.

For this article, you use a Cascading Style Sheets (CSS) and JavaScript library called iUI to handle iPhone content. The iUI library has CSS classes that match Apple's human-interface guidelines for iPhone, as well as JavaScript to handle sideswipes that mimic the interface of the native iPhone OS applications. However, you aren't always going to want to use iUI in your application, so I'll also discuss some of the actual CSS and JavaScript needed to handle those common elements. In keeping with good Rails practice, I factored the HTML for common iUI patterns to Ruby helper methods. These methods are bundled in a Rails plug-in you can download and add to any Rails application.

You will continue to build on the Soups OnLine example used in Part 1. Soups OnLine was used as an example in my book Professional Ruby On Rails and is a site that catalogs soup recipes. In most cases, the specifics of the site are not relevant to this article. The steps involved in putting an iPhone interface together are not tied to the specifics of the Soups OnLine example. The only important detail is that the application, as it exists in its pre-iPhone form, contains a controller called RecipesController that handles listing recipes via its index method. In Part 1, a BrowsersController was added to manage opting in and out of the Mobile Safari version of the site.

To use the examples in this article, you need a Ruby editor or IDE, such as Eclipse. A browser simulator for mimicking an iPhone display is also helpful. Options include the Aptana iPhone plug-in for Eclipse, iPhoney for the Mac, and the official iPhone software development kit (SDK) simulator. Part 1 of this series discusses the installation, usage and pros and cons of each simulator. The examples in this article use the iUI toolkit and the rails_iui plug-in.

iPhone and the user experience

On first seeing an iPhone, the casual observer notices almost immediately that it's just a tiny bit different from a traditional desktop browser. For instance, it's difficult to fit even a normal-size laptop in your front pocket. These differences have a profound effect on how you should structure your Web application for optimal user benefit on iPhone. The most important differences are:

  • The screen size of the iPhone (320x480) is much smaller than even the smallest target application for a desktop Web application. The iPhone screen also has a significantly different aspect ratio from a typical desktop or laptop monitor.
  • The pixel density of an iPhone is much greater than a desktop monitor, allowing small text to be read somewhat more easily and somewhat changing the relative size of images.
  • Users can rotate the Mobile Safari view 90 degrees, changing the size and, more importantly, the aspect ratio of the screen.
  • The touch-screen interface to Mobile Safari is less precise than a mouse interface, meaning that targets like buttons and links should be larger and farther apart than would be necessary in a desktop application.
  • An iPhone is often used under slow network conditions. However, users have a strong expectation that the responses to their actions will be nearly instant.

The result of all these differences is that iPhone Web development cannot be a game of seeing how much you can cram on the screen at once. Even if you could manage to stuff all your navigation bars, logos, ad inserts, and content onto an iPhone screen, either the network slowdown would drive your mobile users crazy or they'd need to sharpen their fingers to tiny points in order to use it. Instead, the goal of iPhone Web development is a clean, simple UI that allows mobile users to get to the features that are most important. In all likelihood, some parts of your Web application will require more taps for a mobile user to access them, but the core of your application will be front and center.

By way of example, Amazon and Digg are two popular Web sites that have created iPhone-specific versions. Digg uses a variant of the iUI framework discussed in this article to mimic an iPhone look and feel, while Amazon uses a more customized look that still works extremely well in the Mobile Safari browser. A picture of Mobile Digg is shown below. (For some reason, Amazon.com doesn't simulate well.)


Figure 1. Digg for iPhone
Digg for iPhone

Both Digg and Amazon have been reduced to their core functionality for mobile users — the list of top stories for Digg, search for Amazon. This focus allows the site to fit into the iPhone screen and gives the mobile user immediate access to the most important site features. Throughout the rest of this article, I show how to fit your site into an iPhone.


Adding iUI to your Rails application

There are two primary options for giving your Web application an iPhone look and feel:

  • Add your own CSS and JavaScript to your site based on Apple's sample code or other good-looking sites.
  • Use a pre-existing toolkit.

The most prominent of the existing toolkits is iUI. The benefit of using it is that the button images, font choices, and JavaScript effects have already been created, allowing you to focus on your site content. The downside is that it has some specific ideas about how your site should be organized:

  • It expects particular Document Object Model (DOM) IDs to be used in specific places.
  • Its default interaction with your server is via Asynchronous JavaScript + XML (Ajax).

I suspect that iUI is best suited for sites that can be easily conceptualized as a list. That said, Apple's human-interface guidelines for iPhone call out the list format as an "especially effective way" of organizing iPhone content, so you should probably consider list organization if you can.

iUI comes packaged as a single directory with the JavaScript file, the CSS file, and a series of images. Since Rails has specific directories where it looks for these kinds of files, you need to integrate the iUI file distribution with your Rails application by:

  • Moving the iui.js JavaScript file to the public/javascripts directory in your Rails application.
  • Moving the CSS file, iui.css, to public/stylesheets.
  • Moving the image files (.png and .gif) go to public/images.

And since, all this movement messes up the relative URLs in the CSS file, you need to change any reference of the form url(button.png) to url(/images/button.png). That way, the CSS file will correctly locate the image in the Rails distribution.

If that seems like too much trouble to do manually, the rails_iui plug-in contains a set of Rake tasks that will download and install iUI, including changing the URLs in the CSS file. The command is rake iui:install. iUI also contains compact versions of the CSS and JavaScript files, with extraneous whitespace removed for a quicker download. The file names are iuix.js and iuix.css. The automated Rake task offers the option of using the compact versions of the files.

When iUI is installed within your project, you need to add the JavaScript and CSS files to your layout. Your iPhone layout file (in this example, it's app/views/layouts/recipes.iphone.erb) should contain the following two lines in the header:

    <%= stylesheet_link_tag 'iui' %>
    <%= javascript_include_tag 'iui' %>
  

If you are using the rails_iui plug-in, that can be expressed merely as <%= include_iui_files %>.

At this point, you're ready to start creating iPhone content.


Creating the iPhone layout

In the original version of the Soups OnLine application, the navigation was in a sidebar and the main content was in the center. That won't work on an iPhone, so I'm going to convert the application to a list structure. The home page of the application will include nearly the same navigation choices presented as a list, and each entry will allow a user to drill down. For example, the navigation entry for Recipes will take a user to another list that shows the most recent recipes added, with an option to show more items. Each of those entries will then link to a display page for the given recipe.

I'm going to discuss the code here on three levels:

  • The Rails helper defined by the rails_iui plug-in
  • The HTML generated by the plug-in using the style classes defined by iUI
  • Some details about the CSS itself, for use in a non-iUI project

By default, iUI overrides the response to a normal link click. Rather than redrawing the entire page, iUI performs an Ajax call and redraws the visible area of the page. This allows iUI to add a sideswipe effect to each link, similar to the effect when drilling down the artist or album list in iPhone's iPod application. You can override this in two ways by changing the target attribute in the anchor tag. If the link target is _self, the normal hyperlink behavior of refreshing the whole page is used. If the link target is _replace, the anchor tag is replaced with the result of the server request.

From a Rails perspective, the iUI structure means that your main layout only gets rendered once. After that, all calls are Ajax. Even your regular link_to calls need to be treated as Ajax calls and delivered with :layout => false. It also means you don't need to use link_to_remote for simple Ajax activity in your iPhone Web application.

So the initial user page for this application is just the navigation. This means you need to set up a default route for the application that renders no text of its own and just displays the layout with the main navigation. Lacking any really obvious place to put that route, add it to the BrowsersController created in Part 1 by adding the following line to your config/routes.rb file: map.root :controller => "browsers".

The controller action goes in app/controllers/browsers_controller and is pretty straightforward.


Listing 1. Default layout route-controller action
                
    layout "recipes"
    def index
      respond_to do |format|
        format.html {redirect_to recipes_url}
        format.iphone {render :text => "", :layout => true}
      end
    end
  

In the iPhone case, it renders just the layout with no text. If the request is for HTML, it redirects to the RecipesController index page, which is the main page of the desktop view of the application.

The iPhone rendering action takes place in the renderer, which now calls a couple of helper functions defined by the rails_iui plug-in to set up the page in accordance with iUI expectations, as shown in Listing 2. (The rails_iui helpers are automatically available to all views when the rails_iui plug-in is placed in the vendor/plugin/rails_iui directory of your Rails application.)


Listing 2. Layout body for iPhone main navigation
                
    <body>
      <%= iui_toolbar "Soups OnLine", new_search_url %>
      <%= iui_list iphone_menu.items,
          :top => content_tag(:h1, "Soups OnLine", :class => "header"),
          :bottom => link_to ("Switch To Desktop View",
              {:controller => "browsers", :action => :desktop},
              :class => "mobile_link") %>
    </body>
  

The resulting screen looks like Figure 2.


Figure 2. Main iPhone Soups OnLine navigation
Main iPhone Soups OnLine navigation

There are two helper functions here. The first, iui_toolbar, sets up the grayish-blue toolbar at the top of many iPhone applications. The Rails helper looks like Listing 3.


Listing 3. Rails helper for iUI toolbar
                    
    def button_link_to(name, options, html_options = nil)
      html_options[:class] = "button"
      link_to(name, options, html_options)
    end
    
    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")
                    else ""
                    end 
      content = [back_button, header, search_link].join("\n")
      content_tag(:div, content, :class => "toolbar")
    end
  

This code results in the HTML shown below.


Listing 4. HTML for iUI toolbar
                
    <div class="toolbar">
      <a href="#" class="button" id="backButton"></a>
      <h1 dddd="header_text">Soups OnLine</h1>
      <a href="http://localhost:3000/search/new" class="button" 
          id="searchButton">Search
      </a>
    </div>
  

Several items in the HTML are defined by iUI. The toolbar class defines the top toolbar color, sizing, and placement. The h1 tag inside the toolbar is also specially defined by iUI for the white text. The backButton DOM ID is reserved by iUI and is created by the iUI JavaScript after a link is clicked. The header_text DOM ID is used by the next rails_iui helper. Listing 5 provides some of the relevant CSS from iUI.


Listing 5. iUI header CSS
                
     body > .toolbar {
         box-sizing: border-box;
         -moz-box-sizing: border-box;
         -webkit-box-sizing: border-box;
         border-bottom: 1px solid #2d3642;
         border-top: 1px solid #6d84a2;
         padding: 10px;
         height: 45px;
         background: url(/images/toolbar.png) #6d84a2 repeat-x;
     }

     .toolbar > h1 {
         position: absolute;
         overflow: hidden;
         left: 50%;
         margin: 1px 0 0 -75px;
         height: 45px;
         font-size: 20px;
         width: 150px;
         font-weight: bold;
         text-shadow: rgba(0, 0, 0, 0.4) 0px -1px 0;
         text-align: center;
         text-overflow: ellipsis;
         white-space: nowrap;
         color: #FFFFFF;
     }
  

The second rails_iui helper, shown in Listing 6, generates the actual list from a list of menu items. The creation of the menu items objects themselves is not important in this context. (For details, check out Professional Ruby on Rails — see Resources). For the purposes of this article, a menu item is an object that has a caption and a URL attribute that describes where the menu item should go when clicked.


Listing 6. rails-iui list helper
                
    def list_element(item)
      onclick_one = "$('header_text').innerHTML='#{item.caption}'; "
      onclick_two = "$('backButton').addEventListener('click', 
          function() {$('header_text').innerHTML='Soups OnLine'; }, false);"
      link = link_to(item.caption, item.option_hash, 
          :onclick => "#{onclick_one} " + " #{onclick_two}")
      content_tag(:li, link)
    end

    def append_options(list_content, options = {})
      list_content = options[:top] + list_content if options[:top]
      list_content += options[:bottom] if options[:bottom]
      list_content
    end

    def iui_list(items, options = {})
      list_content = items.map {|i| list_element(i)}.join("\n")
      list_content = append_options(list_content, options)
      content_tag(:ul, list_content, :selected => "true")
    end
  

Each item in the menu list gets its own li element in the HTML list. It contains a link to the correct URL, plus some JavaScript to manage the toolbar caption. The JavaScript handler does two things. First, it changes the text of the toolbar to reflect the new link. (Since the new link is just making an Ajax call to update the body of the page, this can only be handled client-side.) Second, it changes the handler of the Back button, so that the Back button changes the toolbar header back to Soups OnLine. It's not a full solution to the problem of keeping the header in synch through a deep drill-down, but as I write this, neither iUI or the rails_iui plug-in support this feature.

All the list items are put together in an HTML UL list with the special attribute pair selected=true. iUI uses this to determine which list to place in the body of the iPhone viewport. If there is an HTML tag in the page where selected is set to true, the CSS assigns that to the entire body of the viewport using the CSS declaration display: block. In conjunction with the size definition of the body tag, this effectively gives the selected item the entire viewport. This can be helpful. In one of the iUI samples, several lists are placed on the same page to represent multiple levels of drill down. Only the one listed as selected is displayed initially, and the others are accessed via anchor and name links within the single page.

However, since the selected list is the entire viewport, the header with the Soups OnLine logo and the footer with the Switch to Desktop View link must be placed inside the UL tag. The helper provides options for arbitrary HTML to be included at the top and bottom of the list — you saw them in the earlier snippet from the layout body (Listing 2) as :top and :bottom. The resulting HTML looks like Listing 7. I included the entire listing for the first element in the menu, but skipped the repetitive listings for the other elements.


Listing 7. iUI list HTML
                
    <ul selected="true">
      <h1 class="header">Soups OnLine</h1>
      <li>
        <a href="/recipes" 
          onclick="$('header_text').innerHTML='Most Recent Recipes';   
                $('backButton').addEventListener('click', 
                function() {$('header_text').innerHTML='Soups OnLine'; }, false);">
                Most Recent Recipes</a>
      </li>
      
      ### OTHER LIST ITEMS REMOVED
      
      <a href="/browsers/desktop" 
          class="mobile_link">Switch To Desktop View</a></ul>
  

Clicking on the Most Recent Recipes item gives a sideways swipe and the screen shown in Figure 3, where the header and Back button have been changed by iUI JavaScript.


Figure 3. One level of drill-down
One level of drill-down

To create this screen, the Recipe Controller's index method needs to place format.iphone {render :layout => false} in its respond_to block, as shown below.


Listing 8. Recipe index action
                
    def index
      @recipes = Recipe.find_for_index(params[:type])
      respond_to do |format|
        format.html # index.html.erb
        format.xml  { render :xml => @recipes }
        format.iphone {render :layout => false}
      end
    end
  

The rendered file, app/views/recipes/index.iphone.erb, uses the same rails_iui helper. This assumes that the Recipe object can respond appropriately to the caption and option_hash methods called by the helper: <%= iui_list @recipes %>.


Using replace to extend your list

Earlier, I mentioned that setting the target to _replace in an iUI anchor tag causes the result of the tag call to automatically replace the original list. This is handy to make the last element of your list display something, such as "Next 25 items" and have the new items appear in the same list as the originals, making it easy for the user to scroll up and down the entire list.

To get the replace feature to work using the helpers you've already built, augment the iui_list method in two ways. The list helper needs an option to add the More item to the list — for the moment, assume it's an extra option at the bottom of the list. Then the response to that click needs to return a list of items tagged li, but without the surrounding ul tag, which already exists on the page being modified.

The first part of this implementation is some specific link_to helpers to manage the special _replace and _self behaviors of iUI. Then I added a further method to switch between the various link types based on a target argument. Both are shown below.


Listing 9. iUI link helpers
                
    def link_to_replace(name, options, html_options = {})
      html_options[:target] = "_replace"
      link_to(name, options, html_options)
    end

    def link_to_external(name, options, html_options = {})
      html_options[:target] = "_self"
      link_to(name, options, html_options)
    end

    def link_to_target(target, name, options, html_options = {})
      if target == :replace 
        link_to_replace(name, options, html_options)
      elsif target == :self or target == :external
        link_to_external(name, options, html_options)
      else
        link_to(name, options, html_options)
      end
    end
  

With those link helpers in place, the iui_list helper and the attendant append_options method can be augmented to allow for the new functionality.


Listing 10. iUI link helpers
                
    def append_options(list_content, options = {})
      list_content = options[:top] + list_content if options[:top]
      list_content += list_element(options[:more], :replace) if options[:more]
      list_content += options[:bottom] if options[:bottom]
      list_content
    end

    def iui_list(items, options = {})
      list_content = items.map {|i| list_element(i)}.join("\n")
      list_content = append_options(list_content, options)
      if options[:as_replace] 
        list_content
      else
        content_tag(:ul, list_content, :selected => "true")
      end
    end
  

The extra list element is actually added in line two of the append_options method. The element in question is expected to be passed in the :more option and, like the items list elements, is expected to have a caption and a URL. The final if statement in iui_list causes the ul list tag to be omitted if the :as_replace => true option is passed.

Calling the iui_list method with an extra final link looks like this, where the :more option is used to provide the list element placed at the bottom of the list:

    <%= iui_list @recipes, 
        :more => ListModel.new(nil, "Next 25 items", more_recipes_url) %>   
  

The controller action that responds to more_recipes_url — whatever that turns out to be — is expected to call iui_list with :as_replace => true.

iUI has one other trick with lists. Using the CSS group class gives a header within the list similar to the listing used in the native iPod application for songs.


Figure 4. List with group headers
List with group headers

The rails_iui helper to build the group list reuses most of the code for the plain list. The method takes a block to dynamically determine the headers.


Listing 11. rails_iui helper for list with groups
                
    def iui_grouped_list(items, options = {}, &group_by_block)
      groups = items.group_by(&group_by_block).sort
      group_elements = groups.map do |group, members|
        group = content_tag(:li, group, :class => "group")
        member_elements = [group] + members.map { |m| list_element(m) }
      end
      content_tag(:ul, group_elements.flatten.join("\n"), 
          :selected => "true")
    end
  

The iui_grouped_list method uses the Rails ActiveSupport group_by method, which converts a list into a 2-D list of [group, [members]]. Sorting that ensures that the groups are in alphabetical order. (You want the individual items to be placed in order before they get to this method.)

The view code for this method looks something like this (the block returns the first letter of the title of the recipe):

    <%= iui_grouped_list(@recipes) {|r| r.title[0, 1]} %>
  


Where you are, and where you are going

So far, you've learned how to serve custom content for Mobile Safari users. You've also discovered how to display site navigation using a list look and feel that matches iPhone user expectations and that will load quickly even under slower Edge network conditions.

Part 3 of this series covers what to show when the user is done drilling down and actually gets to some content. This includes display of panels and dialogs and using the common iPhone rounded-rectangle style. You'll also see how to respond to the change when users rotate their devices and flip Mobile Safari sideways.


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.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Web development
ArticleID=318413
ArticleTitle=Developing iPhone applications using Ruby on Rails and Eclipse, Part 2: Displaying iPhone content to the client
publish-date=07082008
author1-email=noelrappin@gmail.com
author1-email-cc=