Categories

jQuery wrapper for iScroll

Matteo Spinelli did a terrific job writing a Javascript library that allows scrolling the content of a DIV element on iPhone and Android web browsers. The library is well documented in his site http://cubiq.org/iscroll. I don’t think that it is a must-have but I find very comfortable to implement functionality in a consistent way. Since I am a big jQuery fan I decided to wrap Matteo’s library in a very simple jQuery plugin.

?View Code JAVASCRIPT
(function($){
    $.fn.iscroll = function(options){
		if(this.data('iScrollReady') == null){
			var that = this;
            var options =  $.extend({}, options);
				options.onScrollEnd = function(){
					that.triggerHandler('onScrollEnd', [this]);
				};
			arguments.callee.object  = new iScroll(this.get(0), options);
			// NOTE: for some reason in a complex page the plugin does not register
			// the size of the element. This will fix that in the meantime.
			setTimeout(function(scroller){
				scroller.refresh();
			}, 1000, arguments.callee.object);
			this.data('iScrollReady', true);
		}else{
			arguments.callee.object.refresh();
		}
		return arguments.callee.object;
	};
})(jQuery);

Basically you can use jQuery to select the content that you want to scroll and call ‘.iscroll()’ to add the behavior. If you need to overwrite the default parameters you pass an object with values that you wish to change. Here is an example of how to implement this:

?View Code JAVASCRIPT
$(function(){
	var elem = $('#content');
		elem.iscroll();
		elem.bind('onScrollEnd', function(e, iscroll){
			alert($(this).attr('id') +' - '+ iscroll);
		});
});

Downloads

When you call .iscroll() the library will find the parent element and make the content scrollable. This method will also return an instance of the iScroll class. If the method is called again it will refresh the content. This is convenient in case you modify the content dynamically (iScroll allows to detech DOM changes automatically).

The library dispatches an event name onScrollEnd when the scroll action is completed. In touch screen devices this library adds momentum. This means that the content will continue moving for a short time after the touchend event. By default the version 3.7 accepts a callback function that is trigger at the end of the animation. The wrapper overwrites the parameter and triggers a jQuery event instead. This approach will allow multiple callback functions.

Implementing doubletap on iPhones and iPads

When the iPhone first arrived, one of the coolest things that Apple did was to have Mobile Safari display a miniature view of an entire web site, allowing users to double tap and zoom into the portions of the site that they wanted to see. This clever solution to navigate large pages in a small screen came with a price.

If your site takes advantage of the double click functionality, when it is displayed on Mobile Safari you loose that capability, your clicks become touch events and the double click (double-tap) now belongs to the browser.

If you want to get the double click functionality back, the first thing you should know is that you can prevent the default browser behavior that takes your double click away. Once you have done that, you can easily re-implement the double click/touch behavior you lost. In your HTML page start with adding a meta tag to indicate the users are not allow to scale the page:

var isiOS = false;
var agent = navigator.userAgent.toLowerCase();
if(agent.indexOf('iphone') >= 0 || agent.indexOf('ipad') >= 0){
       isiOS = true;
}

Now you should be able to write code for iOS devices and for regular browsers

?View Code JAVASCRIPT
if(isiOS){
       // implement double-tap
}else{
       // implement double click
}

Adding double double tap is bit trickier. We need to measure the time between two consecutive touch events. If a user taps twice within 500 milliseconds (half a second), I can assume that the user meant double tap. Now keep in mind that two click/touch events are also fired. The trick is that we have to wait before we can fire a click/touch event and call our doubletap handler only if users click/tap very quickly.

Let’s go one step at the time. I will use jQuery to bind a touchend event to an element selector:

?View Code JAVASCRIPT
$(selector).bind('touchend', function(event){
       var now = new Date().getTime();
       var lastTouch = $(this).data('lastTouch') || now + 1 /** the first time this will make delta a negative number */;
       var delta = now - lastTouch;
       if(delta <500 && delta>0){
               // the second touchend event happened within half a second. Here is where we invoke the double tap code
       }else{
               // A click/touch action could be invoked here but wee need to wait half a second before doing so.
       }
       $(this).data('lastTouch', now);
}

What I have done so far, is to determine the difference between the last touch and the current touch events. The first time that this code runs, both the current touch time and the last touch time are the same, so the delta is zero. At the end of the function I want to make sure that I update the value of the last touch to be equal to the current touch.

Now that we know where to write the double tap code, let’s figure out how to add some logic to help us know when to invoke the double tap code:

?View Code JAVASCRIPT
var action;
$(selector).bind('touchend', function(event){
       var now = new Date().getTime();
       var lastTouch = $(this).data('lastTouch') || now + 1 /** the first time this will make delta a negative number */;
       var delta = now - lastTouch;
       clearTimeout(action);
       if(delta<500 && delta>0){
               // the second touchend event happened within half a second. Here is where we invoke the double tap code
       }else{
               $(this).data('lastTouch', now);
               action = setTimeout(function(e){
                       // If this runs you can invoke your 'click/touchend' code
                       clearTimeout(action);   // clear the timeout
               }, 500, [event]);
       }
       $(this).data('lastTouch', now);
}

To determine how long we have to wait before we can tell that the user only tapped once, I added a timeout call declared outside of the touchend event handler. Every time that the touchend event is fired, I clear the action timeout which prevents the click/touch from happening. If the value of delta is less than 500 milliseconds (half a second) we can definitely invoke the doubletap code, but if delta is higher that 500 milliseconds the timeout is defined again and our click/touch code will be invoked in half a second.

Let’s put everything together and write it as a jQuery extension:

?View Code JAVASCRIPT
/*!
 * jQuery Double Tap Plugin.
 *
 * Copyright (c) 2010 Raul Sanchez (http://www.sanraul.com)
 *
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 */
 
(function($){
	// Determine if we on iPhone or iPad
	var isiOS = false;
	var agent = navigator.userAgent.toLowerCase();
	if(agent.indexOf('iphone') >= 0 || agent.indexOf('ipad') >= 0){
	       isiOS = true;
	}
 
	$.fn.doubletap = function(onDoubleTapCallback, onTapCallback, delay){
		var eventName, action;
		delay = delay == null? 500 : delay;
		eventName = isiOS == true? 'touchend' : 'click';
 
		$(this).bind(eventName, function(event){
			var now = new Date().getTime();
			var lastTouch = $(this).data('lastTouch') || now + 1 /** the first time this will make delta a negative number */;
			var delta = now - lastTouch;
			clearTimeout(action);
			if(delta0){
				if(onDoubleTapCallback != null && typeof onDoubleTapCallback == 'function'){
					onDoubleTapCallback(event);
				}
			}else{
				$(this).data('lastTouch', now);
				action = setTimeout(function(evt){
					if(onTapCallback != null && typeof onTapCallback == 'function'){
						onTapCallback(evt);
					}
					clearTimeout(action);   // clear the timeout
				}, delay, [event]);
			}
			$(this).data('lastTouch', now);
		});
	};
})(jQuery);

Usage:

?View Code JAVASCRIPT
$(selector).doubletap(
    /** doubletap-dblclick callback */
    function(event){
        alert('double-tap');
    },
    /** touch-click callback (touch) */
    function(event){
        alert('single-tap');
    },
    /** doubletap-dblclick delay (default is 500 ms) */
    400
);

This plugin will work on Desktop Browser as well as Mobile Safari. Click HERE to download the source code and basic examples. You can also visit the DEMO page.

Running iPhone Webapps From the Home Screen Only

Webapps are essentially applications that run from a web browser and are tipically served from a controlled environment. We are all familiar with GMail, Google Maps, Pandora, and other great examples of web applications. In the context of mobile devices webapps can run entirely from the device. You can also choose to host data remotely and store the code in the device, or the other way around.

Whatever modality you choose, Safari iPhone allows you to save a shortcut of your web page and open it directly from  the home screen. iPhones, iPad and iPod Touch users are familiar with the “Add to Home Screen” option. The importance of this feature is that helps webapps to look and feel like native applications. Although native apps are more responsive to the user’s input, with appropriate settings  your webapp will not envy anything from a native iPhone application.

Here are some of the things you will need to do to assure a seamless transition from pure web application to iPhone webapp:

Prevent the page from zooming

By default the iPhone renders regular web pages inside a view port that is 980 pixels width. The native resolution of the device is 320px width by 480px height and pages larger that that are proportionally scaled. You can control the way Safari handles the browser’s viewport by adding the following meta tags to the HEAD tag of you HTML code:

<meta name="viewport" content="initial-scale=1.0">
<meta name="viewport" content="maximum-scale=1.0">
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="width=device-width">

As you can see these tags will instruct Safari about how do you want the page to be rendered. If you prefer you can also combine all the settings in one single tag:

<meta 
    name="viewport" 
    content="initial-scale=1.0; 
                    maximum-scale=1.0; 
                    user-scalable=no; 
                    width=device-width">


Add references to a an icon and your startup application image

If you do not specify an icon and a startup image Safari will make a screen shoot of you page and use it as the icon of you application when users add it to the home screen. Safari will also remember the last screen of you application and use is a initial view every time that you app is opened. To prevent the default behavior you can link to two different files that Safari can use as icon and startup image.

<link rel="apple-touch-icon-precomposed" href="my-icon.png">
<link rel="apple-touch-startup-image" href="my-startup-image.png">

The size of your icon must be 57×57 pixels and you startup image must be 320×460 pixels (20 pixels are used to display the top status bar with information about the phone carrier, battery, time, etc.). If you use PNG images with the same proportions Safari should be able to re-scale them. If you are planning to use JPEG it is better to stick to the standard dimensions.

Make sure your webapp will only run from the home screen

If you want user to visit use your web application from Safari iPhone, they will need to do the following:

  1. Open Safari
  2. Tap in the address bar
  3. Type-in your application URL (if the remember it)
  4. Love your application before decide if it’s worthy to bookmark it or add it to the home screen



I know that the following may sound extreme but what if running your webapp from Safari is not an option? In the case of a native application users do not have the option to run an application unless they download it first. If users really hate the application they will probably to delete it immediately (assuming they did not pay for it). If users kinda like the newly downloaded application the icon will stay in the home for future use.

Many people know about an iPhone application name Torch, a very simple app that turns you iPhone into a flashlight. Torch has one screen with the picture of a flashlight and when you tap on it the screen becomes white; tap again and it will restore the previous screen. Easy, right? No question that this application can be reproduced as a webapp. Now, imagine that you drop your car keys in a parking lot and you need Torch to use you iPhone as a pseudo-flashlight. Remember the list about, if Torch was a webapp you will need to open Safari, think hard to remember the URL of the webapp, type it in the address bar, and pray for your cellphone to have signal at that moment (this is assuming you are not drunk coming from a party).

The point is that the chances of your webapp of being used after the first user’s review are limited by Safari browser. If you provide the same type of experience of the AppStore, meaning that you webapp can only run from the home screen when tapping on its icon, user won’t have to think hard to open that webapp a second time. In short, if your webapp is easy to access, the chances of you application of being open more that once are definitely higher.


Enforcing starting up from the Home Screen

If we detect whether or not a webapp is started from the browser or from the home screen, we can tweak the application to make sure the will only atrt from the home screen. Here is quick recipe:


Detect if the webapp has been opened from an iPhone

You will have to decide what is going to be the behavior when the webapp is opened from a regular browser. I will assume for now that the application must run the home screen only.

This is an easy way to detect the iPhone environment:

?View Code JAVASCRIPT
if(navigator.userAgent.indexOf('iPhone') != -1){
	// This the right environment
}else{
	// Display message asking to open the app from an iPhone
}


Run your webapp in Full-screen

You will need to add an extra meta tag in the header of you HTML page to instruct the iPhone to display the page in full-screen mode.

<meta name="apple-mobile-web-app-capable" content="yes">

This only works when that page is loaded from the home button and that is the key. Safari adds an extra property to the browser window.navigator object that specifies whether or not the page is running as standalone (full-screen mode). Your detection code should now look like this:

?View Code JAVASCRIPT
if (window.navigator.userAgent.indexOf('iPhone') != -1) {
	if (window.navigator.standalone == true) {
		// Initialize your app
	}else{
		// Display a message asking to add the app to the Home Screen
	}
}else{
	document.location.href = 'please-open-from-an-iphone.html';
}


Putting everything together

In your webapp is opened from a regular browser you can redirect users to a different page. However, if an iPhone opens the webapp then you can request users to save it to the Home Screen and initialize you application only if the webapp is running in full-screen. The final experience is:

  1. First time: users open you webapp from Safari
  2. A message indicating to add to Home Screen is displayed
  3. If users tap on the plus (+) button, a screen with the custom icon and the title of you page will be displayed
  4. If the users choose to add to Home Screen, Safari will close and the Home Screen will be displayed with the icon of of you webapp
  5. Users can tap on the icon and run the webapp (that’s what we want!)
  6. Next time: Users only need to tap on the icon in the Home Screen and the custom startup image will be displayed while the webapp is loading (voila!)



Sample Code:

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<meta name="apple-mobile-web-app-capable" content="yes">
		<meta name="viewport" content="initial-scale=1.0">
		<meta name="viewport" content="maximum-scale=1.0">
		<meta name="viewport" content="user-scalable=no">
		<meta name="viewport" content="width=device-width">
 
		<link rel="apple-touch-icon-precomposed" href="my-icon.png">
		<link rel="apple-touch-startup-image" href="my-startup-image.png">
 
        <title>My WebApp</title>
    </head>
    <body>
		<script>
			if (window.navigator.userAgent.indexOf('iPhone') != -1) {
				if (window.navigator.standalone == true) {
					initialize();
				}else{
					document.write('<p>Tap the + button and choose "Add to Home Screen"</p>');
				}
			}else{
				document.location.href = 'please-open-from-iphone.html';
			}
		</script>
    </body>
</html>


Resources



Developing Javascript Games for iPhone (part 1)

This is the my first serious attempt to do an iPhone application. Across the board, most of the smart cellphones support some kind of advance web browser that renders Javascript and HTML 5. Many people have already identified this as the common denominator. Webkit, an open source web browser engine is already part of iPhone and Android. It makes a lot of sense to build mobile applications in Javascript, HTML, and CSS and deploy them across multiple devices. Furthermore, frameworks such as PhoneGap and Titanium Appcelerator let you build Ajax applications and publish them as native apps on iPhone, Android and Blackberry.


What a great promise, right?. Mostly true, but there are a couple of things to keep in mind if we want to take advantage of the web browser capability and turn that into a game. I started this little exercise by choose a very simple game that I could write in Javascript in a few hours. The 15 Tile Puzzle game concept took about two hours to prototype.  Adding levels, creating some primitive graphics, debugging and testing, turned this little game into my weekend project.

Writing the game was pretty much as expected. Running the game in the iPhone was a bit disappointing. Although Webkit is an advance browser the game was not as responsive as the example that you see above. I used JQuery for some DOM manipulation as well as for animating the tiles in the game. Unfortunately, all this beauty of building web applications and running them on the iPhone comes with a price:

  • Rendering data is very easy but animating HTML element might be a little bit challenging, at least using JQuery ‘animate’.
  • Click, forget about it. The new devices introduced several new events that take priority over the well known ‘onclick’ event. Apple describe these new events and the sequence of executions in the Safari Web Content Guide.
  • When I used the ‘click’ event, there was a good ~0.5 second delay between the moment when I tap on the the screen and the moment when I saw my application responding. Some people recommend to use the JTouch plug-in, but I found a lot easier using JQuery to bind the new touch events ( $('#elem').bind('touchstart', {foo:123}, function(e){ ... 'e' is the new multitouch event ... }) ).
  • I missed Firebug when testing in the iPhone. Appcelerator has some debugging capability but probably I should look into the starndar iPhone development tools to get something better that ‘log()’.

On the bright side it is still exciting. With very little effort I was able to put together this little game and with the help of any of the framework that I mentioned earlier I will be able to turn my Tile Game into a real iPhone application.

For now if anyone wants to play it on the iPhone you can open Safari and go to http://www.sanraul.com/lab/tilegame/index.php.

I will write the part 2 of this post if I get the Tile Game to the AppStore.
Cheers!