Make Your jQuery Website Buttons/Links Work with iPad/iPhone

May 30, 2011

How to Make your website buttons and links work on the iPad/iPhone without massive customization.

This tutorial requires an intermediate understanding of JavaScript, HTML, CSS, and jQuery. Also, I will supply a sample in plain JavaScript, but this solution works best if you are using the jQuery API to build more cross-browser compatible web pages.

So…
You just completed your latest website for a client. You used jQuery to help eliminate all those pesky browser-specific idiosyncrasies and everything works flawlessly in the dreaded Internet Explorer 7/8/9 as well as all other popular browsers. You have even managed to convinced the client that there really is no need to support versions earlier than IE7.

However, there is one issue on which the client will not bend. They are convinced by all the media-hype that if their new site does not work flawlessly on the iPad that their company will not be taken seriously and their sales will plummet. That is why you decided to give Adobe Flash a miss and stick to the workhorse technologies of HTML, CSS, and JavaScript. How could you go wrong? These are the most ubiquitous technologies on the World Wide Web and are supported (almost) fully in every browser. Also, you have decided to use the jQuery API to handle cross-browser compatibility since it is so ubiquitous and easy to use.

But when you pull up your new site, the one that tested so well in all other browsers, you are finding out that most of your links and buttons are not working properly. Some work, others don’t and no obvious pattern emerges to explain these weird behaviors. What the..? Hey! a link is a link, right? Well, not necessarily. The one thing that most web designers forget about the iPad is that it has no real mouse, so there is no cursor, which means their is no actual “:hover” state for DOM elements. In fact, there are no mouse events at all. They all get synthesized by the iPad and fed to the apps that are listening for them in order to maintain compatibility across websites. At least that is the theory, but like all theories they rarely work as well in practice.

What is Going On?
You see, your web page is at the mercy of the iPad’s web browser, Safari. It looks at your page and IT decides if a page element is clickable or not before it sends the synthesized mouse event. If it doesn’t think an element is clickable then no touch events are generated which means no mouse events get simulated and passed on to your web page. And if the browser doesn’t talk to your page, then your page will just sit, doing nothing, waiting for some sort of input.

Don’t worry, there is a pretty simple solution. Let me explain what I discovered about the iPad and HTML. I found out through a lot of research, that it appears the iPad is looking at your page elements and seeing if they contain an event property called “onclick”. If this property is not on the element then the iPad simply ignores it – even if the users is furiously tapping the element on their iPad. The iPad has determined that it is not an element that can be interacted with – it doesn’t even try to send the event to the element. It just doesn’t bother. View a demonstration of this here.

It seems that natural links, like the <a> tag have this handler by default when they contain the “href” property. So, these types of links tend to work fine with no further modification. But, other elements like the <div> element don’t have this event property by default. They don’t normally need it simply because a <div> is not a link right? Well, it’s not, but many designers use it as a clickable element that contains an image button, label, or other item that they want the user to be able to interact with. The <a> tag is not the most flexible of elements, and sometimes your design calls for other elements acting like links; not trying to force links to act like other elements.

Why Not Just Use Straight Javascript?
Now, if you are using straight, plain JavaScript, then you probably have not run into this problem simply because when you use the traditional method of adding an event listener to a DOM element via Javascript, it handles adding the function to the element for you by way of setting its definition. For example:


// addling an event listener the traditional, plain javascript way.
document.getElementById ("button_3").addEventListener ("mousedown", function () { alert('I was clicked') }, false);

So, why not do all of your event listeners this way? Well, for one thing pre-version 9 Internet Explorer does not work this way. Only newer versions like IE9 deal with event listeners in this way. So, you would have to have two different methods for each DOM element. One to do it the IE way and the other for most other browsers. And that is the biggest reason to use an API like jQuery – it insulates you from the need to code different ways of doing the same thing for different browsers. But, like all solutions, jQuery can also create some unexpected problems. Because jQuery essentially wraps DOM elements in order to make them more flexible and cross-browser compatible, it also insulates your jQuery listeners from the synthesized iPad mouse events which it needs to handle any mouse event-related actions.

I was not looking forward to being required to handle two sets of events for the same actions. I wondered, instead if it was possible to simply force a bridge between the iPad and the web page to get it to synthesize the appropriate mouse events based on its own translation of its touch events. Could I make the iPad believe that the elements I wanted to be buttons and links, really were buttons and links and therefore, it would communicate with my jQuery event mouse event listeners?

My Solution
Well, that is exactly what I did. I simply made sure that if the page was being viewed on an iProduct (iPad/iPod/iPhone) that the “onclick” property was appended to each element before the page ran. This way, the event could pass through the jQuery element wrapper and trigger the element’s own onclick event handler, which then triggers the jQuery event handler in the wrapper! Wonderful! View a demo of this effect on here. You will need to visit this link on your iPad to see it properly, otherwise all of the links will work as expected.

Before I show you the actual code I used (which you can also download here), I want to walk you through the logic of the process:

  • The page loads into the browser and the function “startUp()” is triggered by the page-ready event.
  • I read the user agent string and see if it contains an occurrence of the words “iPad/iPod/iPhone”. Don’t worry about any other identifiers except for these labels, since version numbers will change over time and you do not want your page to break due simply to old age.
  • If the page is NOT on an iProduct, then I ignore the next step and simply display the page.
  • If the page IS an iProduct, then I use jQuery to get an array of all of the classes of elements that I want to make sure have the “onclick” property.
  • After assigning all of the elements an empty “onclick” event, the iPad should now think that it is supposed to pass all simulated mouse events to the page element which can then echo the mouse event to the jQuery event listener for that same element.
  • The page should behave as it does in all other browsers that it functions properly in.

It is actually a pretty simple process, but it took me forever to nail down exactly what the problem with my page elements was.

One warning, though. Simply adding the “onclick” property is not a 100% guarantee that things will work perfectly. On occasion, I have noticed that if an element is nested too many times inside of other elements that it does not always behave predictably. In these rare cases, I simply add my event listener to the parent container that my element is nested inside of.

Okay, now the code. Click here to download all the code in a zip file. You will need jQuery (which is included in the zip download) in order to run it properly.


<html>

<head>
<!-- create a style definition for our button class -->
<style type="text/css">.button { width:250px; height:auto; position:relative; top:50px; left:150px; text-align:center; font-size:14px; color:#FFF; line-height:24px; background-color:#F00; }
.button:hover{ cursor:pointer }
</style>
</head>

<!-- LOAD OUR JQUERY HERE -->
<script type="text/javascript" language="JavaScript" src="jquery-1.6.1.min.js"></script>

<!-- ASSIGN OUR LISTENERS -->
<script type="text/javascript" language="javascript">

// add q jquery listener for the document ready event
$(document).ready(startUp);

function startUp()
{

//see if this is an ipad/ipod/iphone
if( String(navigator.userAgent).search(/(iPad)|(iPhone)/i) > 0 )
{

// add an onclick event handler function for all items that need it so the iPad/iPhone will pass it touch events as mouse events!
$('.onclick_button').each(function(){
this.onclick = function(){ alert( $(this).attr('id') + ": I have an onclick event property!") };
});
}

//now, add regular jQuery listeners to the first and second buttons, but only the second will have the additional onlick property added
$('#button_1, #button_2').live('click',function(){ alert( $(this).attr('id') + ': has received a jQuery event!') });

//also assign a listener the traditional way to button 3 (works in all browsers except IE 8/7/6/5 and lower )
var button3 = document.getElementById ("button_3");
button3.addEventListener ("mousedown", function () { alert('button_3 received a javascript event.') }, false);

};
</script>

<body>

<!-- our first button has NO onclick event property -->
<div id="button_1" class="button">jQuery: no onclick!</div>

<!-- our second link has one added after page ready... -->
<div id="button_2" class="button onclick_button" style="top:150px;background-color:#006600;">jQuery: with onclick!</div>

<!-- our third link has only a plain javascript event listener -->
<div id="button_3" class="button" style="top:250px;background-color:#C90;">Javascript: using addEventListener</div>

</body>

</html>


Follow

Get every new post delivered to your Inbox.