Last week my boss stopped by and dropped a brand spanking new iPad on my desk. "Make our application work on this," he commanded. "You have two days before we demo it at the trade show."
Madness? No, these are web apps! You see, for the last couple years we've been working exclusively on AJAX applications: web pages stuffed with so much JavaScript they look and feel like desktop apps. It's harder than writing desktop software, but if you pull it off you get an application that can be run anywhere, instantly.
So, I'm not a "real" iPhone/iPad developer; I've never even seen the dev kit. I just do web apps. Although maybe "just" isn't the right word. We had a 60,000 line application running on a completely new platform the same week it launched.
Can your app do that?
We already supported Safari, so you'd expect it to work on the iPad too... and it did, mostly, except for a couple of bugs. Flipping the iPad on its side would mess up the layout, double clicks didn't work, stuff like that. Here are the problems we hit, and what we did to solve them.
Browser Detect
To fix a browser-specific JavaScript bug you need to know when you're on that
browser. Detecting the iPad is easy: the iPad user agent contains the unique
string "iPad", so we checked for that.
// a function to parse the user agent string; useful for
// detecting lots of browsers, not just the iPad.
function checkUserAgent(vs) {
var pattern = new RegExp(vs, 'i');
return !!pattern.test(navigator.userAgent);
}
if ( checkUserAgent('iPad') ) {
// iPad specific stuff here
}
Simple enough.
Orientation Change
Flipping the iPad would also mess up our layouts. iPads use an accelerometer to detect if they're being held vertically or horizontally (or upside down.) From the browser's point of view it looks like the window was resized from 1024x768 to 768x1024; and you get the normal window resize event you'd expect.
We use ExtJS, and their Layout framework usually does a great
job handling the window resize event on normal browsers. It knew that
something had happened but didn't recalculate all the sizes correctly. (To
be fair, we use some pretty complicated layouts.)
This is probably do to subtle timing issues or some such. I didn't waste much
time figuring out what was going on, because there's a way to cut this Gordian
knot: the iPad specific orientationchange
event.
window.onorientationchange = function() {
alert(window.orientation);
}
window.orientation is one of 0, 90, -90, or 180. 0 and 180 are portrait; 90 and
-90 are landscape. The onorientationchange event fires whenever it changes.
For us, it was sufficient to say viewport.doLayout()
on orientation change;
that gave ExtJS the hint it needed to get the sizes right.
Double Click
Safari on the iPad co-opts the double click event for its own use (a local
zoom.) You can listen for the dblclick
event in JavaScript... it just never
fires. That was a bit of a problem, because we'd consistently allowed the user
to double click stuff (a row in a grid, for example) to jump to a more detailed view.
We settled on a two finger touch gesture to emulate the double click:
touch an item in one place and tap it in another.
Tapping with two figures
simultaneously also works. When I say emulate, I mean that literally: when
we detect one of these double touches, we actually tell the DOM to fire a
dblclick
event. That way, the solution worked everywhere and we
didn't have to track down every single place we registered a dblclick
listener.
document.body.addEventListener('touchstart', function(e) {
touch = e.touches[0];
if ( !touch ) return;
var me = document.createEvent("MouseEvents");
me.initMouseEvent('dblclick', true, true, window,
1, // detail / mouse click count
touch.screenX,
touch.screenY,
touch.clientX,
touch.clientY,
false, false, false, false, // key modifiers
0, // primary mouse button
null // related target not used for dblclick event
);
touch.target.dispatchEvent(me);
});
Unfortunately, this code is too simple. You see, both the iPad and
iPhone use "two finger scrolling" for scrollable regions (elements with CSS
overflow: scroll;
) within a website.
There's no way to turn this off, and it doesn't even show a scrollbar: it's
two finger scrolling or nothing.
If you use the above code as is, the dblclick event will fire as soon as the
user tries to use two finger scrolling. We ran into this because we have grids
with thousands of rows to scroll from where the user can double click a row to
go to a detailed view. To fix this problem, we had to make sure we only
fired an emulated dblclick
event if the user quickly double tapped a row, not
if they pressed and dragged. So instead we only set up a timer in the
touchstart
event and actually fired the dblclick
from touchend
. This
requires more code, but lets us handle "double clicking" inside of scrollable
regions.
Nitpicking
Pretty much every reviewer has mentioned the iPad's "gorgeous screen," but it's just a 1024x768 display at fairly low DPI, so I'm not sure what that's about. We test everything down to 800x600 so we were ok, usability-wise, but it sure looks a lot better on a large monitor.
We also embed Google maps, using them as backdrops to display geographic data, and the web experience for Google maps is somewhat diminished compared to the full fledged Google Maps iPad app. They still work, though.
Safari on the iPad and iPhone doesn't fire mouse events on elements it doesn't
consider "clickable". Registering a click
event listener, even if it doesn't
do anything, will allow the element to receive the full set of mouse events.
Aftermath
We made the two day deadline.
Did it work? Man, people were stopping by our booth just for the chance to touch an iPad. This was less than two weeks after it launched; there was a lot of curiosity. We use a bunch of data visualization like Google maps and charts, and it was just stunning. It was like a thick, juicy slice of future, served of a piece of future toast. I can't say for sure it generated a sales lead, but it sure brought in some serious foot traffic.
But what do you expect? Web apps can and should run anywhere, even on stuff that hasn't been invented yet.
- Oran Looney April 28th 2010
Thanks for reading. This blog is in "archive" mode and comments and RSS feed are disabled. We appologize for the inconvenience.