Building a maximum-compatability mobile website is a relatively large undertaking, and that's not what I'm talking about here. The latest smartphones have the ability to render HTML quite well, and just require a site design amenable to viewing on a small screen. This post is about one way to provide separate layouts based on the user-agent of the device hitting your website in Ruby on Rails. It is a rather simple approach intended to get you started as opposed to being a production ready example.
How to detect a mobile browser?
So, jumping right in... how do you detect a mobile browser? I'm sure there a fair number of ways, but I chose to use the WURFL library (which you should download - the download link is the first one in the left column of the home page). A quick Google search provided me with the WURFL-Lite gem which provides a thin layer on top of the WURFL data making it quick and easy to integrate it into our site. This is a "big-stick" approach as WURFL contains way more information about the device than we need at this point, but it would allow you to customize your site based on the capabilities of the device in the future. To add the gem to your project just add the following line to your Gemfile:
gem 'wurfl-lite'
Then run bundle install to install the gem on your system. Note that this gem does not store the data in a database, it reads it all into memory - depending on your needs this may or may not be the right approach.
Now that the gem is installed, open the application_controller.rb file - this is where we will be adding some helper methods that will be accessible to all of your controllers and views. First off, we need to load the WURFL data, which is going to take a while so we want to do it as few times as necessary. There are lots of different issues with trying to do this cleanly, so I'm going to start off with a simple approach; I'm going to store it as a class variable like so:
class ApplicationController < ActionController::Base
@@wurfl_db = nil
private
def wurfl_db
#Try getting the data from the cache, this
#significantly speeds up performance in the
#development environment.
@@wurfl_db ||= Rails.cache.read('wurfl_db')
if !@@wurfl_db
@@wurfl_db = WURFL.new "#{Rails.root}/wurfl/wurfl.xml.gz"
Rails.cache.write('wurfl_db', @@wurfl_db)
end
return @@wurfl_db
end
end
To turn on caching add the following line to your environment.rb file:
ActionController::Base.cache_store = :memory_store
NOTE 1: In the development environment class variables are tossed between requests, meaning that we would have to load the WURFL data every time - which is quite slow. Using the rails memory store was a quick and easy solution, although there is still a small but noticeable decrease in responsiveness in the site. If you are trying to run some tests on your dev environment and the extra time taken to load the wurfl data is a problem, then I would suggest temporarily setting the config.cache_classes to true in your development.rb.
NOTE 2: If your web server uses multiple processes you will end up loading the wurfl database once for every process. Depending on how many processes you expect to run, that could be a lot of wasted memory. If that is the case then it might be worth investigating memcached or a similar caching store, or consider moving the data to the database.
Now that we have the WURFL data loaded into memory we can use it to identify the device browsing our site. Create a new helper method:
class ApplicationController < ActionController::Base
@@wurfl_db = nil
helper_method :is_mobile_request?
private
def wurfl_db
#Try getting the data from the cache, this
#significantly speeds up performance in the
#development environment.
@@wurfl_db ||= Rails.cache.read('wurfl_db')
if !@@wurfl_db
@@wurfl_db = WURFL.new "#{Rails.root}/wurfl/wurfl.xml.gz"
Rails.cache.write('wurfl_db', @@wurfl_db)
end
return @@wurfl_db
end
def is_mobile_request?
return !wurfl_db[request.user_agent].nil?
end
end
Now you can call is_mobile_request? from any of your controllers or views to perform custom work for either mobile or desktop.
Choosing your layout
As long as you have built your site using best practices (i.e. putting your styles into your appliction.css file instead of in-lining them in your html.erb files) then you can easily switch between layouts without having to make any changes to your views. You now have a choice, you can modify your existing layout to use the helper we created above or you can create separate layout files. I am going to go with the 2nd approach since I want a clean and obvious separation between my mobile and desktop versions of the site. We can do this right in the application controller:
class ApplicationController < ActionController::Base
@@wurfl_db = nil
layout :determine_layout
helper_method :is_mobile_request?
private
def wurfl_db
#Try getting the data from the cache, this
#significantly speeds up performance in the
#development environment.
@@wurfl_db ||= Rails.cache.read('wurfl_db')
if !@@wurfl_db
@@wurfl_db = WURFL.new "#{Rails.root}/wurfl/wurfl.xml.gz"
Rails.cache.write('wurfl_db', @@wurfl_db)
end
return @@wurfl_db
end
def is_mobile_request?
return !wurfl_db[request.user_agent].nil?
end
def determine_layout
if is_mobile_request?
return "mobile_application"
else
return "application"
end
end
end
Next you will need to create a couple of new files. A public/stylesheets/mobile_application.css stylesheet, and an app/views/layouts/mobile_application.html.erb layout file. You can probably start with a copy of your application.css and application.html.erb. There are a whole bunch of things that you might want to do in your mobile layout and styles that are beyond the scope of this article, but a couple of key items are:
- Load your new mobile_application.css style sheet instead of the application.css
- Add a <meta name="viewport" content="width=device-width, initial-scale=1.0" /> element to the <head> section. This will set the width of your website to the width of the display.
- Update your mobile stylesheet so that clickable items have a fair bit of space between them or are otherwise large. Touchscreen finger presses cover a relatively large area.
- Create icons for your site - this is unfortunately very phone specific, but you can add links to icons that phones will use to identify your site. e.g. <link rel="apple-touch-icon" media="screen and (resolution: 163dpi)" href="icon57x57.png" />
- You may also want to provide the option for overriding the layout choice so that a user can choose to view the mobile or desktop site in case the device detection fails.
Checking that it works
If you have a mobile phone handy then just hitting your website should give you an idea as to whether it is working or not. If you don't have one handy then you can use one of any number of browser add-ons/plug-ins. I use the Modify Headers add-on for Firefox and set a User-Agent header with the user agent string for whichever phone I want to emulate. This is especially useful if you are doing something specific for a particular set if phones but don't have them all on hand to test with. Another option is to set up an Android development environment and created a bunch of virtual devices with different screen sizes and use the emulator to test your site.
Recent Comments