How to avoid code duplication while still providing a mobile view of your application.
With this solution we create subclasses of the controllers along with the alternate views using a subdirectory strategy, so that the mobile interface can be accessed in the following manner:
Standard View: http://yourapp.example.com
Mobile View: http://yourapp.example.com/mobile/
This technique requires the creation of a subdirectory inside of both the controllers and views directories, named “mobile”.
Once the subdirectories are created, subclasses of all page controllers that the mobile interface will access need to be created inside app→controllers→mobile. The exception being anything global controllers, such as the ApplicationController.
Creating a subclass of a \ContactController
Where your standard controller is:
class ContactController < ApplicationController
layout "layouts/standard"...
The subclass of the controller in the mobile subdirectory becomes:
class Mobile::ContactController < ContactController
layout "layouts/mobile_standard"...
Notice how the layout can be altered inside Mobile::\ContactController for the mobile view.
Using a sub-classed controller also allows for custom controller code to handle functionality specific to the mobile view or to override methods used by the standard view.
However, if the mobile view has identical functionality to the standard view, there is no further code to write. The mobile controller inherits all the methods of its parent.
Creating the alternate views
Next copy all the standard view directories, except the layouts directory, into app→views→mobile.
Assuming a standard web interface already exists, copying the standard views into the “mobile” directory creates a fully functional interface for the mobile view.
Note: The layouts directory does NOT get copied into the Mobile directory.
Link the Mobile Views and Controllers to the URL
A route is needed to connect the mobile interface, controllers and URL, such as the following example:
map.connect 'mobile', :controller => 'mobile/start'
An initial starting controller for the mobile controller route is important. If one doesn’t exist, users visiting the mobile interface might fall through to the default route and be served the standard view.
Implement the mobile views
At this point the application will respond to the mobile url with an alternate interface.
Remember to modify any Rails methods, such as url_for which use paths or explicit urls to include the “/mobile” extension. Using methods which explicitly define the controller and/or actions should work as expected.
Example:
In the standard view:
<form action="/start/authenticate" method="post"...
In the mobile view:
<form action="/mobile/start/authenticate" method="post"...
Note: This only applies to code in the views or controllers that explicitly use paths.
All that is left is to implement the mobile views inside app→views→mobile to serve up the slimmed down interface for mobile devices.
The finished directory structure
app:
Things to explore
This is a rough draft and definitely needs editing and improvement. – Lon Baker
Suggestion from left field: If the difference for mobile delivery is chiefly in the layout/view rendered might it not be easier to create a route that sets a flag causing the (shared) controller to render using the mobile-flavoured view instead of the normal one?
# for mobile devices
map.connect "/mobile/:controller/:action/:id", :for_mobile => true
# Install the default route as the lowest priority.
map.connect ':controller/:action/:id'
\ApplicationController#render could be extended to check for the for_mobile flag and adjust the filename accordingly.
We tried something along those lines. Even with help from gurus on the IRC channel we got nowhere.
A pure routing solution would require mixing mobile controller code with standard controller in the event that you wanted different functionality in the mobile view.
One lesson learned in this process, if something is working in development mode on Webrick, it may not work under FastCGI?. Several of the earlier efforts at this technique appeared to work perfectly under Webrick, including altering the template_root (as mentioned on the mailing list), but when we switched to production/fastcgi, they failed.
- Lon Baker
I’m sure a built-in Rails solution to this kind of problem would go a long way for many developers.
— LeeO
You could use some dynamic handling on this, instead of making a whole new set of controllers , one could determine if its able to handle wml or html and proceed accordingly
This snippet:
@request.env“HTTP_ACCEPT”
- ClayC dr dot frog at shaw dot ca
You could try handling the situation in that manner. However that technique does not cleanly allow for differing controller functionality in your mobile view. A mobile interface often needs different features due to the limited bandwidth, screen size and browser capabilities.
I have employed sniffing for the browsers or what they claimed support, but it is always troublesome, and in my opinion bad for the user. Recently I had a client use our mobile interface with his desktop web browser when his internet connection was limited to near modem speeds, not something that is handled well by sniffing.
Also by separating controllers and view templates you never get confused as to what you are looking at. It is cleanly separated from the standard view.
Do you really want to have all your mobile templates mixed with your standard templates, only differentiated by a prefix in the file name? Having to write code to handle which template to send in what case?
With the technique I outlined above, there is no extra code to write (other than the initial subclass of the controllers) unless you desire different functionality for your mobile users. There is no extra work to do to handle different browsers. Just the basic Rails navigation based on the url we have all come to know and love.
I am sure there is more than one way to accomplish this and I hope to see some examples posted.
— Lon
Yes i see your point of view and it is totally valid, some people would prefer the one url notion, and let the rest be sorted out by the server, others your way, i dont think there is any more or less confusion in either way of doing it, if properly thought out.
Layouts can be dynamically generated and the templates held in seperate directories, just as you would , here is a simplistic example
class MyController < ApplicationController
layout :figureitout
private
def figureitout
@request.env["HTTP_ACCEPT"].match("html") ? "html" : "wml"
end
end
Perl has a great CGI::WMLmodule that hands off a lot of the extra work you speak of in generating the proper wml pages , which is very useful, I’ve used it with great results. It would be advantageous to develop the same sort of thing for ruby
There are other issues that do come along with the wml format, most notably language and browser support. The company i worked for solved this with some solid xslt templating , by figuring out where the client was calling in from and what their http user agent is, one can easily display the correct wml
-ClayC : dr dot frog at shaw dot ca
Excellent point. The situation I am concerned about is that many mobile devices contain browsers which accept both content types.
-Lon
Lon, there are many devices out there that specify
To make it more interesting, there are now some devices that are also identifying theirselves are Mozilla. So just pure User-Agent parsing is getting a bit harder.
- Josh
I long for the day mobile browser are up to snuff with the desktop versions. But I am not holding my breath.
Just a quick question/thought. How about a single URL with a button/flag that offers a “mobile” view, or even a drop box with different views? I don’t think there is any method for reliably “identifying” a mobile device.
The WURFL is a great project which helps detect mobile capabilities and accomodate with implementation bugs in their navigator.
-Rom