You may have noticed that we release many of our apps as both locally installed rich clients AND on WAP simultaneously. For example we have a rich client “CheapGas” application and a WAP CheapGas. Some users have asked “do you really write two separate codelines for each way that you deliver these applications?”.

Well of course not. Almost any app developer would get some codesharing here. But in a previous post I discussed how you could write single controllers and controller actions and still support multiple client types. And do it in a way that is much terser and more elegant than the standards Rails 1.2 “responds_to” method that tends to lengthen and obfuscate your controller methods.

But you can go even further than that to allow literally writing one application, with the same controller AND views to support two different client – something I hadn’t seen done before. Leveraging some of the magic of Rails and RXML templates combined with our own Rails helper functions we’ve done just that. For example, the CheapGas app (which I worked on quite a bit personally as a “demo app” before its release) does this: just one controller and view for the app whether its delivered as WAP or HTML or to the Mobio rich client runner.

The way that we do this is to both use RXML templates and also replace the standard HTML tag helper library (which contains functions for creating dropdown boxes, input fields, forms, and so on) with equivalent functions that generate either WAP code, XHTML tags, or tags to our own runner, depending on what kind of device is accessing it. The example .RXML template for the CheapGas search page (which supports our runner, WAP and even XHTML from just one Rails controller and view) is shown below.

xml=doctype(xml) do |x|
 xml.head { }
 body(x) do |x|
   action="station/list" 
   form(action,x) do |x|
     xml.p 'Search by gas stations by named location or zip code' 
     xml.table do
       fields=[]
       xml.tr do   
               xml.td { xml.text 'Select location '}
               xml.td {  xml << select_tag("location",options_for_select(@locations)) }
               xml.td {xml << link_to("<small>(Manage locations)</small>", :controller=>"location")}
               fields << "location" 
       end
       xml.tr do 
               xml.td { xml.text 'Or supply zip code ' }
               xml.td { xml <<text_field_tag('zip')}
               fields <<"zip" 
       end
       xml.tr do
               xml.td { xml.text 'Within radius of '}
               xml.td { 
                 xml << text_field_tag('radius',"style1")
                 xml.text 'miles'
                 fields <<"radius" 
               }
       end 
       xml.tr do
         xml.td { xml.text 'Only prices within last '}
         xml.td { xml << text_field_tag('filtertime')}
         fields= "filtertime" 
       end
       xml.tr { xml.td {submit_tag('Search',action,fields,x)} }
     end # table
   end # form
 end # body
end #doctype

.RXML templates such as the one above let the Rails view be generated as code, instead of the sometimes awkward mixture of tags and code, which of course would then tie the view to a concrete set of tags. Once its all code then “smart helper functions” (select_tag(), text_field_tag(), submit_tag(), form(), body(), head() and doctype()) can be used to generate the tags for each client ype. These functions use the “client type” variable (populated with a Rails :before_filter function as described earlier) to generate the appropriate the appropriate underlying tag output over the wire. The HTML generation is straightforward. Generating WAP and our own rich client tags is more involved.

The result for the app developer is one set of code, both controller and views, to deliver multiple output types. This is something I haven’t seen in Rails apps (or other apps for that matter) whether its targeting mobile scenarios or otherwise.

Just what it means to generate tags which generate our rich client interfaces (i.e. what is the output if the client type is “Mobio Runner”) is the subject of several future posts.