Markup for Desktop and Mobile
So you know how to use the viewport meta tag to control some things about how mobile browsers render your page, you know about media queries and how you can use them to tweak your css for different sizes of monitors, and you know about progressive enhancement, and that everything doesn't actually have to look the same on every browser. Now let's get very practical. Let's say you want to build a website that will work for both desktop and mobile. You can do this easily in some circumstances but in others it can be a harder task. Here are some questions to ask yourself.
The Questions
None of these questions should be seen as absolutes. Rather, see them as questions for thought.
First, would you characterize your site primarily as an app or as a content delivery mechanism? If your site is there to allow people to do something, like read and send email, is a reader for blog posts, helps people organize their tasks, etc., it's more like an app. If your site is there to allow people to consume information, like a blog or a news site, its more of a content delivery mechanism. In my experience if your site is more like an app, it is harder to keep the same Html, Css and JavaScript for your desktop and mobile experiences. If your site is like a blog, splitting your client-side assets is probably a terrible idea.
Second, how complicated is your app going to be? This question is not all that different than the previous. If the app is going to have a very complex UI, you have a higher chance that you will need to split your Html, Css and JavaScript because the different screen sizes may require very different ways of doing things. But this is of course not an absolute.
Third, what is your mobile support level going to be? Are you going to support modern desktop browsers, iPhone and Android? If so, you have a much better chance of not having to split your markup. Are you going to support old Blackberry devices or old feature phones? If so, you have absolutely no hope of using the exact same markup, unless you have a terribly bland site in both function and design.
Fourth, what kind of interactions are you going to have? If you will be making a touch-centric site, it will likely make little sense on a desktop monitor, so planning for major differences makes sense. Otherwise you have a better chance of making your Html, Css and Javascript work well on phones, tablets and desktops.
Fifth, how do form factor and context influence the app's design and functionality? If either the form factor or the context significanly influence what the app will do, then you are going to have a harder time sharing Html, Css and JavaScript. Here is a concrete example that I heard someone did (do not recall who did this). Let's say you are creating a site for a conference. Viewed in a desktop browser you got general information about the conference. Viewed in a phone, you get the agenda front and center because that's what people at the conference would want to see. Perhaps you would want to do something similar.
Sixth, are you adding mobile support to an existing site. If so, there's a bigger chance you will want to have different client-side assets or (more likely) different sites (at least at first) because you start a site very differently if you have mobile in mind.
Avoiding Pain
Let's think about it in an entirely way. In some ways, how you will handle this is a question of pain avoidance. Though code is fun to write, more code means more time as well as more to maintain, so the more we have to write and debug, the more pain. Let's get concrete.
Recently I made a mobile version of this site that should work pretty well on iPhone, Android and Windows Phone 7. I could have made a completely different mobile site, but that would have been a lot more work. I could have created new mobile markup for the site, but that would potentially double the markup that I wanted to maintain, and that sounds like pain. The easiest solution, by far, was to make my blog mobile by using media queries. With just a little Css, this site is optimized for mobile viewing.
Here is another example. In April we launched a new mobile website at work (Match.com). As of this posting, it supports both iOS 4 and 5 devices (though more support is coming). If you have an iPhone, you can check it out at mobi.match.com. We did not try to take the existing primary website and just make it mobile. We would have to make quite a bit of changes to the Html, Css and Javascript to optimize the site for this and in doing so we would have created a host of dependencies between my team (the mobile team) and the main site team. We would have also endangered the primary site because the refactoring and extension to functionality would have undoubtedly introduced bugs. And on top of that, the mobile app has a very different design than the desktop app. In theory that could all be done with just a bit of Css, but the designs are very different, so that would definitely not have been enough. So having a separate site, with completely different code makes a lot more sense.
But even within our site we plan on having multiple sets of Html, Css and JavaScript. What we have works great for iOS and pretty good for some other platforms (tweaking in progress), but there is no way the current mobile site will work on old Blackberries and feature phones. So even within our site we are going to have to handle this and we want to do this with as little pain as possible.
Options
So how should you approach this? I will show you how to do it two different ways using ASP.NET MVC, but if you are using a different platform you could probably do something similar. So let's clarify what exactly we want to do. What we want to do here is be able to server up different Html depending on the user agent of the browser who is requesting our page. If it is a device running iOS, we want to use a Foo.ios.cshtml file. If it is a device running Android, we want to use a Foo.android.cshtml file. Otherwise, we'll use the default skin, Foo.cshtml. And if there isn't an override skin, drop down to the default skin.
Custom View Engine
So let's do this! If you want to kick it old school (before version ASP.NET MVC 4), you would probably do this by creating a custom ViewEngine implementation. Here's a simple version.
public class FancyViewEngine : RazorViewEngine
{
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
var determiner = new DeviceClassDeterminer();
string deviceClassName = determiner.GetClassName(controllerContext.RequestContext.HttpContext.Request.UserAgent);
if (!String.IsNullOrEmpty(deviceClassName))
{
string controllerName = controllerContext.RouteData.Values["controller"].ToString();
string filePath = String.Empty;
if (viewName.Contains("~"))
filePath = viewName.Replace(".cshtml", "." + deviceClassName + ".cshtml");
else
filePath = "~/views/" + controllerName + "/" + viewName + "." + deviceClassName + ".cshtml";
string layoutPath = "~/views/shared/_layout." + deviceClassName + ".cshtml";
if (!File.Exists(controllerContext.RequestContext.HttpContext.Server.MapPath(layoutPath)))
layoutPath = "~/views/shared/_layout.cshtml";
if (File.Exists(controllerContext.RequestContext.HttpContext.Server.MapPath(filePath)))
{
var view = new RazorView(controllerContext, filePath, layoutPath, true, new List<string> { "cshtml" });
var result = new ViewEngineResult(view, this);
return result;
}
}
return base.FindView(controllerContext, viewName, masterName, useCache);
}
}
You will also need the implementation of that DeviceClassDeterminer class. Here you go:
public class DeviceClassDeterminer
{
public string GetClassName(string userAgent)
{
userAgent = userAgent.ToLower();
if (userAgent.Contains("android"))
return "android";
else if (userAgent.Contains("iphone os"))
return "ios";
else
return String.Empty;
}
}
And finally, you need to hook this up. You can do this in your global.asax, or elsewhere if you so choose.
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new FancyViewEngine());
}
}
Now all you need are .cshtml files that follow the proper naming convention and you are good to go. Tweak as necessary.
DisplayModeProvider
If you are using ASP.NET MVC 4, you have a new bit of functionality built in, the DisplayModeProvider.
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("ios")
{
ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf("iPhone", StringComparison.OrdinalIgnoreCase) >= 0)
});
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("Android")
{
ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf("Android", StringComparison.OrdinalIgnoreCase) >= 0)
});
}
}
That's all you have to do. It knows how find the proper .cshtml files, both for regular page and layouts.
Which To Use?
In many cases it will be just a matter of preference, but there is more you can do if you create your own ViewEngine. But the DisplayModeProvider is easier to you, so it's a tradeoff.
But What About Css and JavaScript?
Good question! Fortunately, most of that problem is solved by this approach. If you want different Css and JavaScript for Android, you just reference the files you need in the layout page for Android and leave them out of the other layouts. That will at least get you most of the way there. We will talk about this more in the future.