Chapter 10

Going Mobile

In recent years, users have been moving away from desktop computers and relying more and more on mobile devices to browse the web. The mobile growth rate has been staggering, especially outside of North America. In many developing countries, users are skipping over the desktop computer and browsing the web for the first time on a mobile device.

For instance, Asia saw a 192 percent growth rate in mobile traffic share from 2010 to 2012, according to Pingdom (http://royal.pingdom.com/2012/05/08/mobile-web-traffic-asia-tripled). And it's not just an Asian phenomenon: The worldwide increase in that period is over 162 percent.

Although desktop browsing still outpaces browsing on mobile devices, that is projected to change as early as 2014. That's an enticing opportunity, and many companies are positioning themselves to reap the rewards of mobile traffic. Mobile-first has become a common mantra among many developers, who are now gearing their apps toward mobile devices, with desktop support as an afterthought. That's a remarkable shift from a couple of years ago when mobile apps were tacked onto the primary offering: desktop web apps.

In this chapter, you find out how to position your web app in the mobile space. You first learn how to detect mobile devices and create responsive layouts that adjust for different screen sizes and device orientations. Then you learn about a couple of scripts you can employ to handle the mobile viewport and to serve responsive content, followed by a few mobile frameworks you can use to streamline your mobile development.

After building a firm foundation in mobile development, you dive right in, handling multi-touch events and complex finger gestures using the Hammer.js library. This library helps you to leverage the touch screen interface in your apps, which can respond to swipes, taps, and more user interaction.

Next you discover how to use the geolocation API to deliver localized content to your app users based on their devices' GPS. You also learn how to dial phone numbers and send text messages using JavaScript. Finally, you learn how to build native device apps with PhoneGap and JavaScript. PhoneGap allows you to bridge the gaps between your JavaScript app and the various APIs that are unavailable at the browser level. By the end of this chapter, you'll have a nice introduction to a variety of key skills for mobile development.

Setting Up a Mobile App

With the overwhelming popularity of mobile, it just doesn't make much sense to build a web app for only desktop browsers. Unless there's literally no way to get your app working on mobile, you should definitely build a version for smartphones and tablets. But that doesn't mean you have to start from scratch and build an entirely separate app. In fact, it's a good idea to avoid segmentation and create a more universal codebase for both desktop and mobile.

Detecting Mobile

The first challenge to setting up a mobile version of a site or app is detecting whether the user is on a mobile device in the first place. Normally, it's best to do this on the server-side so that you can send pared-down content and reduce the size of the markup for slower mobile connections. You can then send mobile-specific JavaScript and avoid any detection on the front end.

However, sometimes it makes sense to handle this detection with JavaScript. Unfortunately, that's going to mean browser-sniffing with user agents. There's just no reliable way to detect mobile without the user agent, since device manufacturers have derailed all the proposed methods.

Device manufacturers deliberately dodge mobile detection so they can display the full-scale desktop site and make their devices seem more impressive.

Finding User Agents

Detecting mobile with the user agent presents its own challenges. With new devices and mobile browsers coming out regularly, it can be difficult to keep an up-to-date list of mobile user agents. Fortunately, there are a number of services that handle it for you.

• The simplest approach is to download the JavaScript implementation from http://detectmobilebrowsers.com. However, the script just provides a simple redirect—so you'll need to modify it to create a hook you can use in your JavaScript, such as setting a variable isMobile. Additionally, bear in mind that you'll have to update the script over time as user agents change.

• If you need finer-grained detection, you should use WURFL: http://wurfl.sourceforge.net. WURFL (Wireless Universal Resource FiLe) is a frequently updated XML file that lists not only mobile browsers but also a variety of their device-specific capabilities. Thus, although it relies on user-agent sniffing, it's still reasonably reliable for feature-detection.

The only downside is that you'll have to set up an API to use it in your JavaScript. Fortunately, there are a few available, such as the Tera-WURFL remote client for PHP: http://dbapi.scientiamobile.com/wiki/index.php/Remote_Webservice.

Discovering Orientation

Once you know the user is on a mobile device, you can also check the device orientation with JavaScript. The trick is to use window.orientation, which outputs the orientation in degrees. You can use it to set up a simple switch for portrait and landscape modes:

switch( window.orientation ) {

  case 0:

  case 180:

    // portrait mode

  break;

  case 90:

  case -90:

    // landscape mode

  break;

}

Landscape mode can be either 90 or -90 degrees, depending on which way the device has been rotated. Additionally, you can track when the orientation of the device changes, using window.onorientationchange:

window.onorientationchange = function() {

  alert('new orientation: ' + window.orientation + ' degrees'),

};

In iOS, you can tap into much more precise information about the device using window.ondeviceorientation. It can tell you exact rotational measurements from the device's gyroscope and even tap into the accelerometer. For more information, visit http://www.peterfriese.de/how-to-use-the-gyroscope-of-your-iphone-in-a-mobile-web-app.

Using Media Queries to Resolve Layout Issues

Finally, you can use media queries with JavaScript if you're detecting layout-based issues. For instance, here's how to set up a switch based on the window size:

if (window.matchMedia('(max-device-width: 480px)').matches) {

  // devices that are 480px or smaller

}

However, bear in mind that this script detects any matching device, mobile or otherwise.

Styling a Mobile Site

In the past, mobile styling was an HTML/CSS thing. You used media queries, or back-end mobile detection to display different stylesheets and HTML techniques to display the modified site within the mobile viewport. However, recently there's been a trend to handle some of this with JavaScript. It's not that the traditional approaches don't work. It's that with a variety of device-specific issues, they don't work well enough.

Viewport Script

One such example is handling the mobile viewport, meaning the scale that the page is displayed on the mobile screen. In the past, you handled this with a basic HTML meta tag:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />

In theory this approach should work reasonably well to show the page at a certain zoom level. However, in practice, it leaves a lot to be desired. The main issue is rotating. You've probably noticed on your device that when you rotate to landscape, the screen zooms to match the scale of the device in portrait mode. While that's good if you're reading a paragraph, it's not necessarily the best user experience for most sites.

Fortunately, the folks at Zynga released an open-source JavaScript that handles viewporting much better. You can download it here: https://github.com/zynga/viewporter.

The script handles the rotating issue much more gracefully and also provides some other niceties, such as removing the top bar of the browser.

Another Viewport Script

Another issue with viewports is that they can be pretty arbitrary when it comes to content. Why should the user's screen size determine the breakpoints of a page, potentially cutting off images and paragraphs onto the next screen?

When it comes to responsive designs and viewports, the conventional approach is to set fixed breakpoints. That approach works pretty well if you need to support only a couple of devices, but it fails miserably with the vast number of different mobile screen sizes out there. Fortunately, there's a script that sets up viewports according to the content on the page, which you can download here: https://github.com/bradfrost/ish. With this script, you define a few bucket sizes and let it automate the rest.

Responsive Images Script

Another common problem when serving a mobile version of a website is how to handle images. On the one hand, you want to display small images to mobile; on the other hand, you want to display high-res images to desktop browsers. You could handle that by serving completely different markup from the server, but that can be a bit difficult to build and maintain. There is an easier solution that uses some interesting tricks with the image paths, which you can download here: https://github.com/filamentgroup/Responsive-Images.

Basically, you define paths to both of the images you want to use, and the script uses JavaScript to determine the best one to use. However, the paths work only on an Apache server, so you'll need to explore other options if you're not using Apache. Fortunately, there are a number of different scripts out there, which you can read about here: http://css-tricks.com/which-responsive-images-solution-should-you-use.

Mobile Frameworks

As a result of the popularity of mobile devices, a large number of mobile libraries and frameworks are now available. These resources provide a giant head start for your mobile development efforts. However, just like with any third-party tool, it's important to select the one that best suits your project.

Sencha Touch: Sencha Touch is probably the most robust mobile framework. It provides a number of useful mobile components, as well as an MVC and styling. It's a perfect fit for very interactive apps that need a lot of mobile-specific functionality. So if you're building a fully-featured mobile app, look at Sencha Touch. But if you're just building a mobile skin for a simple brochure website, Sencha Touch is major overkill. Of all the mobile frameworks, it has the highest learning curve and largest filesize. To learn more about Sencha Touch, visit www.sencha.com/products/touch.

jQuery Mobile: jQuery Mobile is another framework that provides a variety of mobile components and themes. It's built on jQuery and jQuery UI, so most developers find it easy to pick up and hit the ground running. jQuery Mobile provides a lot of styling, and a themer similar to jQuery UI, so it can be really great for rolling out a mobile UI without design comps. But if you do have design direction, it can be a little more challenging to work with. jQuery Mobile is best for web apps that don't need all the functionality of SenchaTouch. To learn more about jQuery Mobile, visit http://jquerymobile.com.

Mobile libraries: If you're just building a mobile skin for a simple website, avoid the mobile frameworks. You'll be a lot happier with mobile libraries if you want to inject a small amount of mobile functionality to your site. In Chapter 2, you read a bit about Zepto.js. Zepto is a lightweight JavaScript library that includes a few basic touch gestures you can use for mobile. Find more about Zepto's touch events here: http://zeptojs.com/#touch. Additionally, in the next section, you discover how to use Hammer.js to add robust touch-gesture support to your app.

Touch Screen Integration

One of the particular challenges of mobile development is handling a different interface: the touch screen. But you shouldn't think of it as a challenge, so much as an opportunity to provide a higher quality UX.

At the time of this writing, touch screens are mostly a mobile phenomenon. But some touch-screen monitors have already been produced for desktop computers, and that trend is likely to continue in the coming years as the line blurs between desktop and mobile.

Basic Touch Events

To interface with the touch screen, you have to create handlers for a new type of event: the touch event. But you may be wondering why you'd even set up handlers for touch events. After all, mobile browsers still register click events when the user touches the screen. However, while a mouse can click only a single point on the screen, touch screens can register multiple touch events at a time—as many as users have fingers.

Creating a Single Touch Event

Creating a handler for a single touch event is simple:

var el = document.getElementById('my-element'),

el.addEventListener('touchstart', function(e) {

  // what to do on touch

});

Here the script uses the standard JavaScript addEventListener() method to attach the touchstart event. This event fires the moment the user's finger touches the screen, similarly to mousedown.

Likewise, you can also bind touchend, which fires when the user removes his or her finger (similar to mouseup). There's also a third event, touchmove, which fires when the user places his or her finger on the device and then moves it.

Creating Multi-Touch Events

When using touch events, you can get the coordinates of the touch point from the event object:

el.addEventListener('touchstart', function(e) {

  var x = e.touch.pageX;

  var y = e.touch.pageY;

});

As you can see here, touch.pageX and touch.pageY give you the (x,y) coordinates of the point on the screen the user touches. But you can also get information about multiple touch points—if the user is touching the screen with more than one finger. For that, you need the touches array:

el.addEventListener('touchstart', function(e) {

  // first finger

  var x1 = e.touches[0].pageX;

  var y1 = e.touches[0].pageY;

  // second finger

  var x2 = e.touches[1].pageX;

  var y2 = e.touches[1].pageY;

});

Here the event relays the coordinates of the first finger on the screen in touches[0] and the second finger in touches[1]. Additional touch points will be added to this array (touches[2] for the third finger and so on).

Complex Touch Gestures

Probably the best part about coding for the touch screen is the opportunity to use touch gestures, such as swiping, pinching, and rotating. These gestures improve the UI of mobile apps, providing a much more interactive experience.

Although you can program your own gestures, doing so can get pretty complicated. Fortunately, a number of gesture libraries are available, most notably Hammer.js (http://eightmedia.github.com/hammer.js). Hammer.js makes it easy to include a wide variety of touch gestures for different mobile platforms such as iOS, Android, and Blackberry.

iOS includes some native gesture support. But you'll most likely end up building fallbacks for other devices, so it's best to just stick to Hammer.js gestures.

Hammer.js Basics

Using Hammer.js is really easy. First, download the source and include it on your page. Next, bind it to an element:

var hammer = new Hammer( document.getElementById('my-element') );

Finally, define a touch event on that object:

hammer.ondoubletap = function(e) {

  console.log('Whoah the element has been double tapped'),

}

This event fires whenever the user double taps the element. But ondoubletap is just the beginning. Hammer.js includes many different gestures, such as ondrag, onhold, and ontransform.

Slideshow with Hammer.js

One of the most common website elements is the image slideshow. To create a better mobile experience, you can enhance basic slideshows with gesture controls. That way the user can swipe left and right to change the image.

Creating the Slideshow

First, start with some markup:

<section class="slideshow">

  <div class="slides">

     <img src="images/my-image.jpg" alt="" class="slide" />

     <img src="images/my-image-2.jpg" alt="" class="slide" />

     <img src="images/my-image-3.jpg" alt="" class="slide" />

     <img src="images/my-image-4.jpg" alt="" class="slide" />

  </div>

</section>

As you can see here, the slideshow markup is kept very simple.

Next add some styling:

.slideshow {

  width: 500px;

  height: 300px;

  position: relative;

  overflow: hidden;

}

.slides {

  position: absolute;

  top: 0;

  left: 0;

  width: 10000px;

  -webkit-transition: left 0.3s ease;

     -moz-transition: left 0.3s ease;

       -o-transition: left 0.3s ease;

          transition: left 0.3s ease;

}

.slide {

  width: 500px;

  height: 300px;

  float: left;

}

The idea with the styling here is to float all the .slide images within the .slides wrapper to make a long horizontal reel. This wrapper is then absolutely positioned in the parent .slideshow container.

That way you can move the .slides wrapper left and right to show different slides in the reel. And rather than animate the changes in the JavaScript, the code uses some simple CSS transitions.

When building for mobile, it's a good idea to animate with transitions since they are much better for performance.

Finally, add some basic jQuery:

function Slideshow($wrap) {

  this.currSlide = 0;

  

  // cache some variables

  var $slideWrap = $wrap.find('.slides'),

  var slideWidth = $slideWrap.children(':first-child').width();

  var slideCount = $slideWrap.children().length;

    

  this.changeSlide = function() {

    // sanity check on currSlide

    var $kids = $slideWrap.children();

    if ( this.currSlide >= $kids.length ) this.currSlide = 0;

    else if ( this.currSlide < 0 ) this.currSlide = $kids.length - 1;

    

    // change the horizontal position of the slides

    $slideWrap.css('left', slideWidth * this.currSlide * -1);

  };

  

  // change slides on an interval

  var slideInterval = setInterval( $.proxy( function() {

    this.currSlide++;

    this.changeSlide();

  }, this), 4000);

}

var slideshow = new Slideshow( $('.slideshow') );

The slideshow script is really straightforward so far:

1. First it sets up a public this.currSlide variable and caches a few variables for later.

2. Then it creates a public this.changeSlide() function, which changes the displayed slide based on the this.currSlide variable.

3. this.changeSlide() checks to make sure the new slide isn't outside the bounds of the slideshow. Then it moves the .slides wrapper based on the index of the new slide and the width of the slides. The animation is then handled with the CSS transition that was established earlier.

4. Finally, an interval is set up to change the current slide every four seconds. One thing to pay attention to here is the use of $.proxy(), which passes the context of this into the interval callback.

The script changes the slides on an interval, but you can set up any type of functionality you want. Just use the this.changeSlide() function to create next and previous buttons, numbered navigation, and so on.

Adding Swipe Gestures

So far the slideshow is working on an interval, changing the slide every four seconds. The next step is to add swipe gestures for mobile. First, download Hammer.js from http://eightmedia.github.com/hammer.js and include the main script on your page. Hammer.js also includes a jQuery plugin, but for now you're just going to use the stand-alone script.

Using Hammer.js is pretty easy; first you create a new instance of Hammer on whichever element you want to bind the touch events and then define a handler. For example:

var hammer = new Hammer( document.getElementById('my-element') );

hammer.onswipe = function(e) {

  // whatever you want to do on swipe

};

In the slideshow example, you want to bind this event to the main wrapper, so add the following within the Slideshow() function:

var hammer = new Hammer( $wrap[0] );

$wrap[0] grabs the DOM reference of the jQuery object in the script and uses that to bind Hammer.js.

Next, bind the actual swipe event:

var hammer = new Hammer( $wrap[0] );

hammer.onswipe = $.proxy(function(e) {

  switch( e.direction ) {

    case 'left':

      this.currSlide++;

      this.changeSlide();

    break;

    

    case 'right':

      this.currSlide--;

      this.changeSlide();

    break;

  }

}, this);

The swipe event is first bound using the same $.proxy() method you saw earlier. That way the context of the slideshow instance is retained in the onswipe event handler. Next it uses the direction key from the event object to determine whether the user is swiping left or right. It then increments the currSlide variable accordingly and finally calls the changeSlide() function.

If you reload the slideshow in a mobile browser, you see that the swipe events are working. However, there is still more work to be done.

First, the swipes also move the page—you still need to prevent the default behavior of this touch event. Additionally, whenever you have a slideshow with an interval, it's a good idea to remove the interval once the user starts interacting with the controls—that way the script doesn't hijack whatever the user is trying to do. Making these changes is easy:

var hammer = new Hammer( $wrap[0] );

hammer.onswipe = $.proxy(function(e) {

  // clear any interval

  clearInterval( slideInterval );

  

  switch( e.direction ) {

    case 'left':

      this.currSlide++;

      this.changeSlide();

    break;

    

    case 'right':

      this.currSlide--;

      this.changeSlide();

    break;

  }

  

  // prevent default behavior

  return false;

}, this);

Here the interval is being cleared first. Then the default behavior is prevented with return false. Unfortunately, Hammer.js doesn't support preventDefault() at the time of this writing, except with a global option that is a little too heavy-handed here.

If the swipes aren't working exactly as you want, consider changing some of the options for Hammer.js. You can find out more about these options here: https://github.com/eightmedia/hammer.js#defaults. Try adjusting the swipe_time and swipe_min_distance.

Rubber Banding

Now the swipe gestures are working, but the slideshow acts a bit weird at the top and bottom bounds because it loops around to the other side. That works well in the interval, but it's not the best experience for the swipe events. Fortunately, it's easy to prevent the looping on the swipe events:

var hammer = new Hammer( $wrap[0] );

hammer.onswipe = $.proxy(function(e) {

  // clear any interval

  clearInterval( slideInterval );

  

  switch( e.direction ) {

    case 'left':

      // stop if at the bounds

      if (this.currSlide >= slideCount - 1) {

        return false;

      }

  

      this.currSlide++;

      this.changeSlide();

    break;

    

    case 'right':

      // stop if at the bounds

      if (this.currSlide <= 0) {

        return false;

      }

  

      this.currSlide--;

      this.changeSlide();

    break;

  }

  

  // prevent default behavior

  return false;

}, this);

Now the script is stopping the loop, but that doesn't relay much information to the user—it just looks like the slideshow is broken. It's a much better idea to set up rubber banding—moving the slideshow slightly past the bounds and then snapping it back into place. Rubber banding is a very common paradigm in mobile UIs, and one with which the user will most likely be familiar. Fortunately, with the CSS transitions already set up, it isn't too difficult to accomplish:

var hammer = new Hammer( $wrap[0] );

hammer.onswipe = $.proxy (function(e) {

  // clear any interval

  clearInterval( slideInterval );

  

  switch( e.direction ) {

    case 'left':

      // stop if at the bounds

      if (this.currSlide >= slideCount - 1) {

        // rubberband it

        var thisPosLeft = slideWidth * -1 * (slideCount - 1);

        $slideWrap.css('left', thisPosLeft + slideWidth / 3 * -1)

    

        setTimeout(function() {

          $slideWrap.css('left', thisPosLeft);

        }, 200);

    

        return false;

      }

  

      this.currSlide++;

      this.changeSlide();

    break;

    

    case 'right':

      // stop if at the bounds

      if (this.currSlide <= 0) {

        // rubberband it

        $slideWrap.css('left', slideWidth / 3);

    

        setTimeout(function() {

          $slideWrap.css('left', 0);

        }, 200);

    

        return false;

      }

  

      this.currSlide--;

      this.changeSlide();

    break;

  }

  

  // prevent default behavior

  return false;

}, this);

Take a look at the rubber banding in the right swipe—because it's a little easier to follow. First, it moves the slide one third of the way out; then it sets a timeout to snap it back into place. The opposite is set up for the left swipe.

Wrapping Up

Now the slideshow is working with both the swipe gestures and rubber banding. Here's the entire script:

function Slideshow($wrap) {

  this.currSlide = 0;

  

  // cache some variables

  var $slideWrap = $wrap.find('.slides'),

  var slideWidth = $slideWrap.children(':first-child').width();

  var slideCount = $slideWrap.children().length;

    

  this.changeSlide = function() {

    // sanity check on currSlide

    var $kids = $slideWrap.children();

    if ( this.currSlide >= $kids.length ) this.currSlide = 0;

    else if ( this.currSlide < 0 ) this.currSlide = $kids.length - 1;

    

    // change the horizontal position of the slides

    $slideWrap.css('left', slideWidth * this.currSlide * -1);

  };

  

  // change slides on an interval

  var slideInterval = setInterval( $.proxy ( function() {

    this.currSlide++;

    this.changeSlide();

  }, this), 4000);

  

  // make swipeable

  var hammer = new Hammer( $wrap[0] );

  

  hammer.onswipe = $.proxy(function(e) {

    // clear any interval

    clearInterval( slideInterval );

    

    switch( e.direction ) {

      case 'left':

        // stop if at the bounds

        if (this.currSlide >= slideCount - 1) {

          // rubberband it

          var thisPosLeft = slideWidth * -1 * (slideCount - 1);

          $slideWrap.css('left', thisPosLeft + slideWidth / 3 * -1);

      

          setTimeout(function() {

            $slideWrap.css('left', thisPosLeft);

          }, 200);

      

          return false;

        }

    

        this.currSlide++;

        this.changeSlide();

      break;

      

      case 'right':

        // stop if at the bounds

        if (this.currSlide <= 0) {

          // rubberband it

          $slideWrap.css('left', slideWidth / 3);

      

          setTimeout(function() {

            $slideWrap.css('left', 0);

          }, 200);

      

          return false;

        }

    

        this.currSlide--;

        this.changeSlide();

      break;

    }

    

    // prevent default behavior

    return false;

  }, this);

};

var slideshow = new Slideshow( $('.slideshow') );

To recap what's going on here:

1. The script first defines a changeSlide() function, which sets a new horizontal position for the .slides wrapper. This position is in turn animated by the transitions in the CSS.

2. An interval is set up to show the slides in a reel in all browsers, mobile and desktop.

3. Hammer.js is instantiated on the slideshow, and a swipe gesture is bound, with a switch for left and right swipes.

4. In each swipe direction, a rubber banding animation is triggered if the swipe is past the bounds of the slideshow. Otherwise, it uses the changeSlide() function to switch to the new slide.

Hammer.js's Transform Gestures

In addition to swiping, iOS users have become familiar with a few more gestures, such as pinching and rotating. Typically, the pinch gesture is used to scale elements larger or smaller, and the rotate gesture is used to rotate them. iOS provides native events for these gestures, but you can enable cross-platform support using Hammer.js.

Creating a Box That Scales and Rotates

First, adjust the meta viewport of the page to disable any erroneous scaling:

<meta name="viewport" content="width=500, user-scalable=no"/>

Then start with a simple element:

<div id="transformer"></div>

And apply some basic styling:

#transformer {

    background: tomato;

    width: 300px;

    height: 300px;

    margin: 50px auto;

}

Next attach Hammer.js to this element and define a transform handler:

var wrapper = document.getElementById('transformer'),

var hammer = new Hammer( wrapper );

hammer.ontransform = function(e) {

  wrapper.style.webkitTransform = 'rotate(' + e.rotation + 'deg)' + ' scale(' + e.scale + ')';

  

  return false;

};

Here the transform handler uses the CSS property -webkit-transform to apply the scale and rotation changes, which are accessed with e.rotation and e.scale. Now when you load this page in a mobile device, the element scales and rotates with your finger gestures.

Don't worry about other vendor prefixes for the transform CSS. The devices Hammer.js supports all use WebKit.

Caching Transformation Changes

However, if you try another gesture it gets a bit wonky. Since the script doesn't cache the new scale and rotation values after each gesture, it restarts each gesture from the beginning. Fortunately, that's easily solved:

// cache rotation and scale

var rotation = 0;

var scale = 1;

// bind hammer.js

var wrapper = document.getElementById('transformer'),

var hammer = new Hammer( wrapper );

// transform event – modify CSS

hammer.ontransform = function(e) {

  wrapper.style.webkitTransform = 'rotate(' + (e.rotation + rotation) +

  'deg)' + ' scale(' + (e.scale * scale) + ')';

  

  return false;

};

// transform end event – cache new values

hammer.ontransformend = function(e) {

  rotation += e.rotation;

  scale *= e.scale;

};

Here the script starts by caching rotation and scale variables. Then it uses these to adjust the values in the ontransform handler. Finally, it caches the new values in the ontransformend handler, which fires at the end of the gesture. It can't simply change the cached values in the normal ontransform handler, or they'll pile up too fast. Now the script saves any previous transformations, so multiple gestures work as expected.

Geolocation

One advantage to mobile sites is that they can provide specific information based on the user's physical location in the world. For instance, if you're trying to find the nearest pizza shop, why should you enter in your zip code when your device already knows exactly where you are?

Finding the User's Location

Fortunately, tapping into the geolocation API is easy with JavaScript:

if ( navigator.geolocation ) {

  navigator.geolocation.getCurrentPosition(function(position) {

    console.log('Location: ' + position.coords.latitude + ' ' + position.coords.longitude);

  });

}

Here the script first checks whether geolocation is available and then uses getCurrentPosition() to return the user's latitude and longitude. Of course, there are inherent privacy issues with geolocation, so whenever you make a call to the API, the user's browser or device will prompt them to accept it, as you can see in Figure 10-1.

9781118524404-fg1001.tif

Figure 10-1 The user must grant access to geolocation data, as you can see from this iOS device.

Bear in mind that even in non-mobile devices, geolocation calls will return something based on the user's IP.

Connecting with Google Maps

Once you have the user's latitude and longitude, you can do a number of things with it—for instance, tap into an API to get his or her city name or relaying the data to the Google Maps API.

First, obtain a Google Maps API key by following the instructions here: https://developers.google.com/maps/documentation/javascript/tutorial#api_key. Then, to show a map of the user's current location, use the following code:

<!DOCTYPE html>

<html>

<head>

  <title>Current Location</title>

  <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />

  <style type="text/css">

  html, body, #map_canvas { height: 100% }

  body { margin: 0; padding: 0 }

  </style>    

</head>

<body>

  <div id="map_canvas" style="width:100%; height:100%"></div>

    

  <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&

  sensor=true">

  </script>

  <script>

  // get current position

  if ( navigator.geolocation ) {

    navigator.geolocation.getCurrentPosition(function(position) {

      displayMap( position.coords.latitude, position.coords.longitude );

    });

  }

  // show map

  function displayMap(latitude, longitude) {

    // define map options

    var mapOptions = {

      center: new google.maps.LatLng(latitude, longitude),

      zoom: 10,

      mapTypeId: google.maps.MapTypeId.ROADMAP

    };

    

    // pull the map from the Google API

    var map = new google.maps.Map(document.getElementById("map_canvas"),

      mapOptions );

  }

  </script>

</body>

</html>

This script first sets up some basic styling and markup and then references the Google Maps API script. Make sure to fill your API key into the YOUR_API_KEY value. Next, the script uses navigator.geolocation.getCurrentPosition() to pull the user's latitude and longitude. It then relays that information to a displayMap() function. The displayMap() function simply passes the latitude and longitude values into the Google Maps API and renders the map. That's all there is to it.

Now, fire up this page in your mobile device (or desktop browser), and you should see something similar to Figure 10-2.

9781118524404-fg1002.tif

Figure 10-2 The geolocation script is displaying a map of my current location in Portland, OR.

As this example demonstrates, interfacing with the API is pretty straightforward. You can then take it to the next level, creating a custom map to your business location or placing pins for relevant locations close to the user.

Tracking Geolocation Changes

You can also track changes to the user's position over time using watchCurrentPosition():

if ( navigator.geolocation ) {

  navigator.geolocation.watchCurrentPosition(function(position) {

    console.log('Location: ' + position.coords.latitude + ' ' + position.coords.longitude);

  });

}

This script sets up an event handler to track changes in geolocation. It's a lot better than simply setting up getCurrentPosition() with an interval, since it fires only when the location changes. watchCurrentPosition() can also be useful even when you don't need to track a moving target. Mobile devices often return a rough geolocation as soon as one is available and then refine it as more precise data becomes available.

Using watchCurrentPosition() ensures that you pull the most accurate location data once it's available. But to save resources, you should remove the handler after it fires a few times:

var watchCount = 0;

var positionTimer = navigator.geolocation.watchPosition(function (position) {

    // increment the count

    watchCount++;

    // clear the watch on the third attempt

    if ( watchCount > 2 ) {

      navigator.geolocation.clearWatch(positionTimer);

    }

});

Using clearWatch() allows you to save resources when you want a refined geolocation but don't need to actually track changes over time.

Phone Numbers and SMS

With JavaScript, you can even dial phone numbers and send text messages on certain devices. Don't get too excited; it won't start the call directly in the browser. But you can use JavaScript to route users into the phone or messaging app in their device.

Static Phone Numbers and SMS Links

The trick to dialing phone numbers and sending texts is built upon the HTML5 markup standard for phone links:

<a href="tel:+12125551234">(212) 555 - 1234</a>

This code dials the given phone number if the user clicks the link from a capable device. Additionally, you can set up links for text messaging:

<a href="sms:+12125551234">Text me</a>

When a user clicks this link, his or her text-messaging client opens with the given number. You can even set up texts to multiple recipients:

<a href="sms:+12125551234,+12125556789">Text us</a>

Dialing Phone Numbers and Texting with JavaScript

You can also use the tel: and sms: protocols to interface with phone features directly from JavaScript. It's really simple; you just call these links with window.location:

// call (212) 555-1234

window.location =  'tel:+12125551234';

Now, when you call this script, it interfaces with the user's phone. For instance, in iOS, it triggers a prompt the user can accept or cancel, as shown in Figure 10-3.

9781118524404-fg1003.tif

Figure 10-3 Dialing a number with JavaScript causes this prompt in iOS. The prompt avoids any malicious use of the protocol.

Likewise, you can send text messages:

// text (212) 555-1234

window.location = 'sms:+12125551234';

However, when interfacing with the device's phone functionality, you need to make sure that you're on a capable device. It's not enough to simply detect mobile because many mobile devices such as tablets and iPod Touches do not include a phone.

PhoneGap

So far in this chapter, you've learned how to build a mobile web app that users can access through their device's browser. However, sometimes you want to build an app they can install directly on the device. Previously, that meant writing a different app in native code for each platform: Objective-C for iOS, Java for Android, and so on. Wouldn't it be nicer if you could just roll out a single app across a number of mobile platforms?

PhoneGap makes that possible, allowing you to write a dedicated mobile app with the HTML, CSS, and JavaScript skills you already have. PhoneGap then packages that app into code that can run natively on a variety of devices, including iOS, Android, Blackberry, Windows Phone 7, Symbian, and more.

Best of all, since the code runs as a native app, PhoneGap is able to expose device functionality that is not available at the browser level because of security issues. PhoneGap provides JavaScript APIs for a number of native features, such as:

• Accelerometer

• Geolocation and compass

• Media capture (photo, audio, and video)

• Media playback

• Notifications (alert popups as well as sound and vibration notifications)

• File structure and storage

• Contact list

• Connection type (WiFi, 2G, 3G, and so on)

To see more native features, go to http://docs.phonegap.com.

The open source arm of PhoneGap is called Apache Cordova.

Pros and Cons of PhoneGap

While PhoneGap supports a wide variety of functionality, it's important to remember that it's not for every project.

PhoneGap Versus Native Code

PhoneGap renders apps using web views instead of each platform's native UI framework. That's decidedly worse for performance than building an actual native app. The fact is that PhoneGap makes it easy to roll out native apps. It's great for projects that already have a web app that you want to convert to a native device app, which means that you have to do very little programming to release your dedicated app.

As always, you get what you pay for. Natively coded apps are always going to be better than PhoneGap apps, but they also tend to cost a lot more to develop, especially if you're planning on releasing on multiple platforms.

Native Apps Versus Web Apps

You also have to decide whether you need a native app at all. Depending on your situation, a standard web app might be a better fit. Web apps are less commitment for users because they don't have to download and install something new to their device. Ask yourself: Would I want to download this app or simply access it through the browser?

Another piece of this decision is whether you want to involve the iOS App Store or Android Market. These can be a blessing when it comes to distribution or a curse when it comes to the 30 percent in fees they take (which is not to mention the enigmatic approval process).

Keep in mind that web apps can still be saved to the homescreen on many devices, which means the user can still click on an icon from his or her phone to pull up your app.

Getting Started with PhoneGap

Although PhoneGap can roll out your code to a number of different mobile platforms, the roll-out process isn't as simple as flipping a switch. Each platform deployment works a bit differently and must be performed separately. But it's still a lot easier than writing each app from scratch. For more information about how to get started for each platform you want to support, visit http://docs.phonegap.com/en/2.2.0/guide_getting-started_index.md.html.

Also, keep in mind that not all of the APIs are available in every device or platform. For a compatibility table, visit http://phonegap.com/about/feature.

Connecting with the Camera

Once you have PhoneGap set up, using device functionality is easy because PhoneGap exposes native device features with simple JavaScript APIs. For instance, you can access the device's camera using the getPicture() method:

navigator.camera.getPicture(onSuccess, onError, {

  quality: 60,

  destinationType: Camera.DestinationType.DATA_URL

});

function onSuccess(imageData) {

  // create a new image

  var img = new Image();

  img.src = "data:image/jpeg;base64," + imageData;

  

  // append it to the DOM

  document.body.appendChild( img );

}

function onError(message) {

  alert(message);

}

When the app runs this script, it opens up the native camera dialog on the device and then passes that data back into the app. The getPicture() method accepts both a success and error callback, along with an options object. In this example, the camera relays the image as a data URI, which is then appended to the DOM.

Alternatively, you could save this data on a server or even tap into the file system of the device to store the image.

Although most devices don't provide camera access at the browser level, Android supports the Media Capture API, which you can use to take pictures even without PhoneGap.

Connecting with the Contact List

It's unlikely that mobile devices will ever provide browser-level access to the data in the contact list. The information is just too sensitive. However, native apps are able to access this data, and PhoneGap apps are no exception. Simply take advantage of the contacts API:

navigator.contacts.find('*', function(contacts) {

  console.log(contacts);

}, function(error) {

  alert(error);

}, {

  multiple: true

});

Here the find() method returns a list of all contacts. Similarly to the camera API, this method accepts success and error callbacks, as well as an options array.

Getting contact information can be very useful for certain apps, for instance if the user wants to find friends to play games against, or share a photo.

Other APIs

Hopefully, these quick examples have given you an idea of how easy it is to use PhoneGap's APIs. But they're really just the tip of the iceberg. PhoneGap includes a variety of additional APIs you can leverage in your mobile app.

You can access the accelerometer and gyroscope, record audio and video, save and load files in the device's storage, and much more. For more information, visit the PhoneGap Docs: http://docs.phonegap.com/en/2.2.0/index.html.

Summary

In this chapter, you discovered a variety of mobile development skills. You first learned how to detect mobile devices and create responsive layouts that scale for different screen sizes and device orientations. Then you looked into a couple of easy scripts that you can use to handle mobile viewports and deliver responsive content and images.

Next you learned about multi-touch devices and how to set up gesture handlers with Hammer.js. Then you learned about localized content using the geolocation API as well as how to use the device's phone features—sending phone calls and text messages using JavaScript.

Finally you read about PhoneGap and how to build native mobile apps using the JavaScript skills you already have.

I'm sure you understand the importance of developing a mobile app. Now you have the skills to make it happen.

Additional Resources

Mobile Usage Stats

Google Mobile Planet: www.thinkwithgoogle.com/mobileplanet/en

Ofcom Report: www.smartinsights.com/marketplace-analysis/customer-analysis/new-internet-usage-report

Pingdom Report: http://royal.pingdom.com/2012/05/08/mobile-web-traffic-asia-tripled

Mobile Detection

Detect Mobile Browsers: http://detectmobilebrowsers.com

WURFL: http://wurfl.sourceforge.net

Tera-WURFL Remote Client: http://dbapi.scientiamobile.com/wiki/index.php/Remote_Webservice

Mobile Frameworks

SenchaTouch: http://www.sencha.com/products/touch

jQuery Mobile: www.jquerymobile.com

Mobile Libraries

Hammer.js: http://eightmedia.github.com/hammer.js

Zepto.js: http://zeptojs.com

jQTouch: http://jqtouch.com

Mobile Styling Tools

Viewporter (Zynga): https://github.com/zynga/viewporter

Ish: https://github.com/bradfrost/ish.

Responsive Images (Filament Group): https://github.com/filamentgroup/Responsive-Images

Which Responsive Images Solution Should You Use?: http://css-tricks.com/which-responsive-images-solution-should-you-use

Additional Mobile Tools

iOS Gyroscope and Accelerometer: www.peterfriese.de/how-to-use-the-gyroscope-of-your-iphone-in-a-mobile-web-app

PhoneGap Docs: http://docs.phonegap.com

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset