Table of Contents

W3C Specification

This specification highlights features (SQL, offline application caching APIs as well as online/offline events, status, and the localStorage API) from HTML5 and provides brief tutorials on how these features might be used to create Web applications that work offline.

This specification defines APIs for a database of records holding simple values and hierarchical objects. Each record consists of a key and some value. Moreover, the database maintains indexes over records it stores. An application developer directly uses an API to locate records either by their key or by using an index. A query language can be layered on this API. An indexed database can be implemented using a persistent B-tree data structure.

Promise returning functions should never throw, they should return rejections instead. Throwing from a promise returning function will force you to use both a } catch {and a .catch. People using promisified APIs do not expect promises to throw. If you’re not sure how async APIs work in JS – please see this answer first.

1. DOM load or other one time event:

So, creating promises generally means specifying when they settle – that means when they move to the fulfilled or rejected phase to indicate the data is available (and can be accessed with .then).

With modern promise implementations that support the Promise constructor like native ES6 promises:

4. A whole library with node style callbacks:

There is no golden rule here, you promisify them one by one. However, some promise implementations allow you to do this in bulk, for example in Bluebird, converting a nodeback API to a promise API is as simple as:

Promise.promisifyAll(API);

Notes:

Of course, when you are in a .then handler you do not need to promisify things. Returning a promise from a .then handler will resolve or reject with that promise’s value. Throwing from a .then handler is also good practice and will reject the promise – this is the famous promise throw safety.

In an actual onload case, you should use addEventListener rather than onX.

Mobile WebKits attempt to synthesize touch events into into click events. These same browser engines also have a double-tap-to-zoom function. This brings up the issue of distinguishing between a tap and double tap. What is a WebKit to do?

The answer is pretty simple. WebKit adds a small delay of 300 milliseconds after a single tap to see if you will tap a second time. If no second tap happens within 300 milliseconds, the gesture is interpreted as a single tap which is subsequently translated into a “click” event.

Great!

You already know that you can disable pinch and tap zooming in your app, provided you have designed your app to have nice, large buttons and text. Unfortunately, even after you do this, WebKit maintains the 300 millisecond delay after a single tap. To make matters worse, the delay is only triggered on fast taps. If you touchstart, pause briefly, then touchend you will experience no delay. Most users will tap fast enough to experience the delay some, if not most, of the time. This small delay can create a perceived slowness in your app, and therefore, it must be eliminated with extreme prejudice.

Libraries like fastclick will eliminate this delay for you, and will also take care of some tricky edge cases, but basic fast tap handling is pretty easy. The example code below demonstrates fast tap handling with the jQuery special event API, but the process will be similar with most other JavaScript event-handling libraries.

$.event.special.tap={setup:function(){varself=this,$self=$(self);// Bind touch start$self.on('touchstart',function(startEvent){// Save the target element of the start eventvartarget=startEvent.target;// When a touch starts, bind a touch end handler exactly once,$self.one('touchend',function(endEvent){// When the touch end event fires, check if the target of the// touch end is the same as the target of the start, and if// so, fire a click.if(target==endEvent.target){$.event.simulate('tap',self,endEvent);}});});}};

This is the basis for any sort of fast tap handling. In some cases, this may even be good enough. However, there are many ways to make this more robust.

Time Threshold

If a user starts a touch, holds, and then releases after one second, is that a tap? Maybe not, here’s how you can add a configurable time threshold to abort a tap event if the time between touchstart and touchend is too long.

$.event.special.tap={// Abort tap if touch lasts longer than half a secondtimeThreshold:500,setup:function(){varself=this,$self=$(self);// Bind touch start$self.on('touchstart',function(startEvent){// Save the target element of the start eventvartarget=startEvent.target,timeout;functionremoveTapHandler(){clearTimeout(timeout);$self.off('touchend',tapHandler);};functiontapHandler(endEvent){removeTapHandler();// When the touch end event fires, check if the target of the// touch end is the same as the target of the start, and if// so, fire a click.if(target==endEvent.target){$.event.simulate('tap',self,endEvent);}};// Remove the tap handler if the timeout expirestimeout=setTimeout(removeTapHandler,$.event.special.tap.timeThreshold);// When a touch starts, bind a touch end handler$self.on('touchend',tapHandler);});}};

Distance Threshold

If the user starts a touch, moves 200 pixels to the left, then releases, is that a tap? Maybe not. Here’s how you can add a configurable distance threshold to abort a tap event if the user moves his or her finger too far.

$.event.special.tap={// Abort tap if touch moves further than 10 pixels in any directiondistanceThreshold:10,// Abort tap if touch lasts longer than half a secondtimeThreshold:500,setup:function(){varself=this,$self=$(self);// Bind touch start$self.on('touchstart',function(startEvent){// Save the target element of the start eventvartarget=startEvent.target,touchStart=startEvent.originalEvent.touches[0],startX=touchStart.pageX,startY=touchStart.pageY,threshold=$.event.special.tap.distanceThreshold,timeout;functionremoveTapHandler(){clearTimeout(timeout);$self.off('touchmove',moveHandler).off('touchend',tapHandler);};functiontapHandler(endEvent){removeTapHandler();// When the touch end event fires, check if the target of the// touch end is the same as the target of the start, and if// so, fire a click.if(target==endEvent.target){$.event.simulate('tap',self,endEvent);}};// Remove tap and move handlers if the touch moves too farfunctionmoveHandler(moveEvent){vartouchMove=moveEvent.originalEvent.touches[0],moveX=touchMove.pageX,moveY=touchMove.pageY;if(Math.abs(moveX-startX)>threshold||Math.abs(moveY-startY)>threshold){removeTapHandler();}};// Remove the tap and move handlers if the timeout expirestimeout=setTimeout(removeTapHandler,$.event.special.tap.timeThreshold);// When a touch starts, bind a touch end and touch move handler$self.on('touchmove',moveHandler).on('touchend',tapHandler);});}};

Other considerations

Reading the fastclick source is the best way to understand all the nuances of fast click handling on touch devices. Fastclick will take care of some quirks in older mobile browsers, different requirements for certain input elements, as well as eliminating “phantom” clicks.