The Uncertain Web: Pointer Event Polyfill and Chrome Fragmentation

Fat Stacks

The Uncertain Web might be out, but that doesn’t mean that I’m done talking about the current state of the open web platform. I’m going to get back to writing a little bit here (as there are no more books on the immediate horizon) and as part of that I’m going to cover some topics that relate to the book under this “the Uncertain Web” headline. Two such topics have bubbled to the surface in recent weeks.

Chrome Fragmentation

Peter Paul-Koch wrote about the ongoing fragmentation of Chrome (specifically Chromium based browsers) last week. He’s identified 11 separate versions on mobile and he details his findings in his post “Chrome continues to fall apart at brisk pace.

There are a lot of things about this that tie into what I’m talking about in the book.

For starters, you might assume that if you test in Chrome on a single Android device or, even worse, test in Chrome on the desktop with just the emulation feature of the Developer Tools you’re fine since it’s all “just Chrome.” That’s clearly not the case. This is exactly what I’m talking about when I urge you to “question your assumptions.”

Secondly, the importance of testing with real devices can’t be understated. I outlined a luxury testing strategy in the book that targeted a half dozen separate Android versions. As PPK’s article illustrates even that is not enough to get the full picture. Still, even falling short of a perfect testing plan, getting a variety of different devices running different Android flavors is important to ensure that you’re really testing your site or application and aren’t merely kicking the tires. Multiply that across iOs, Windows, and whatever else strikes your fancy and you’ll have a robust testing regime for the current state of the web.

Finally, this Chromium soup illustrates the importance of sticking to the tried and true methods of web development that we’ve established over the past decade and a half. Building baseline experiences that work across the broadest range of browsers and devices is going to be the best way to reach the widest possible audience. You shouldn’t have to worry about what browser people are using. 11 versions of Chrome shouldn’t keep you up at night. As long as you’re paying attention to features, using feature detection and device characteristics using media queries you’re going to be better off than if you’re sitting around fixating on the different versions and trying to piece together the differences into some bizarre matrix of pain.

I know there are a lot of people out there that have started to put together sites that only work in Chrome (including knuckleheads from Google that really ought to know better and should be setting a better example for the rest of the web.) That’s a terrible idea to begin with as there are hundreds of millions of people on other browsers out there. Then you factor in this inability to define what “Chrome” even is, and it’s clear that this new-fangled “this site is best viewed in Chrome” trend is even worse than the old-school “this site is best viewed in Internet Explorer” trend. At least back then it was a monoculture with just a handful of variations of IE in the wild and the browser’s penetration was nearly 90%.

Put some PEP in your Step

Since it’s an important aspect of modern web development that remains confusing for many developers, I wrote a whole chapter in the book on dealing with user input on the web. The importance of properly handling user input on the web should be obvious. If you can’t handle clicks of the mouse and taps of the finger on a screen, you’re not going to get very far as a web developer. It’s a complicated mess and plenty of smart people screw it up.

Check out Patrick H. Lauke’s Getting touchy presentation for the best possible overview (that doesn’t involve me re-writing a whole book chapter here.)

The tone of the chapter in The Uncertain Web was initially a hopeful one. There’a technology out there, Pointer Events, created by Microsoft and adopted by the W3C that unifies user input (mouse, finger, stylus, whatever) into a single API. When I initially wrote the chapter, there was support in Internet Explorer (obviously,) intent to implement from Firefox and an open issue to do the same (behind a flag) in Chromium. If all three had fallen into line then there would have been some possibility of forcing Safari’s hand and getting them, too, to implement this simplified interface. That was my hope at least and my tone (originally) reflected that.

Them, during the technical review for the book, the Chromium issue was closed. The Chrome team had decided against implementing Pointer Events in favor of unspecified “enhancements” to the existing Touch APIs So, I had to rewrite the chapter and cast Chrome as the villain in this particular tale. It was a bummer. Especially as, at the same time, the Polymer team deprecated support for their Pointer Events Polyfill. Suddenly we were down one browser and had no supported, high quality polyfill for the technology.

That’s the way the book went out the door. User input on the web was still a mess and Google was the villain.

Then, as the book was finally making its way onto the shelves it was announced that the jQuery foundation was taking over maintenance of the Pointer Events Polyfill. At least in my house, there was much rejoicing.

It’s still early days in the transition, but the project is already active on Github. I’m watching the project. You should too.

Pointer Events are, whatever Google thinks, a better solution to handling user input on the web. Beyond it being easier to code, and it is, it also does away with the conceptual problem many people have with this corner of web development. Despite serious effort from a lot of people, developers still think of it as a binary “touch” or “mouse” choice. It’s not.

If people start to think about “pointers” as a unified interface maybe people will stop doing things like the following example I’ve pulled from Wired.

If you visit Wired.com with a touch enabled laptop, mouse clicks simply don’t work. To activate many interactive elements you have to touch the screen. It’s super annoying.

Here’s why it happens.

Looking at their code, they’re using a ternary operator to create an event alias named touchity, by using crude test to set the event name as touchstart on browsers with touch capability and click on everything else:

var touchity = ( ('ontouchstart' in window) ||  (window.DocumentTouch  && document instanceof DocumentTouch) ) ? 'touchstart' : 'click'; 

So, later on, when events are bound and touchity is passed into jQuery’s $.on, under the hood, only touchstart or click is set:

$('.wp35-gallery .nav').add('.curtain').on(touchity, function(e) 
{
  autoPlay('forceStop');
  if (isThisADoubleTap()) {
    e.preventDefault();
  }
  if ($(this).hasClass('next') 
      || (event.target.id === 'curtain-right')) {
    offsetSlide(1);
  } else {
    offsetSlide(-1);
  }
}); 

This breaks on any device with a mouse and a touch screen. For a personal example, with my current set-up I’ve got a touch enabled laptop set up as a workstation 90% of the time. The laptop itself has a touch screen. The external monitor does not. When I visit wired, I get the “touchstart” events even though the screen I’m using isn’t touch enabled. This means the browser being touch-enabled is true on one screen, but not the other, no matter what the browser API reports to their crude test.

To activate a slideshow or other link on wired.com I need to drag the window from the external monitor to my laptop, reach up and touch the screen.

Sigh.

Pointer Events helps to make fundamental conceptual errors like that go away because you never think about whether it’s touch or mouse or stylus or three dimensional gesture or eye tracking or thought control or whatever. You just have a pointer.

So, follow the project, use the polyfill and then we can all bug Google to get back on the side of the angels.

Easy Autocomplete with the datalist Element, the list Attribute and AngularJS’s ng-repeat Directive

Continuing to write about some Angular features I’ve been working with recently, I thought it would be interesting to show off one of the little conveniences I was able to put together using just a couple of Angular’s core features. This example leverages basically two basic features of Angular and two HTML5 features to create an easy, live autocomplete widget.

The pieces we’re going to use are:

  • Angular models This demo shows how easy it is to use Angular’s models. While they can get more complicated, this example shows the base case of simply binding to a variable in scope, creating dynamic links that update the UI when the data is updated with minimal or no JavaScript interaction.
  • ng-repeat Obviously if you’ve worked with Angular for any length of time you’ll be familiar with this directive. Here, it’s used here to iterate over a collection and create a dynamic datalist element.
  • datalist and list The datalist element and the list attribute are the two new HTML features that bind up the autocomplete functionality. In short, the datalist element defines a list of autocomplete entries. The list attribute binds the datalist to an input using an id reference. This combination is available in Chrome, Opera and Firefox with buggy support in IE10+

Let’s see how it all goes together.

First up, since this is Angular, let’s look at the markup.

To get started you need to define an Angular controller. This one is named MainCtrl. Since this is just a simplified demo to illustrate the autocomplete widget, it doesn’t do much, but the code it’s pulled from is a form used to add information about individual comic books to a simple database. Many of those comics are from the same titles (Amazing Spider-Man, Action Comics, etc.) so it’s handy to have an autocmplete list in order to improve data entry efficiency.

The input itself has two pieces to note, the data-ng-model attribute and the list attribute.

Note: As always, I append data- to Angular attributes in order to follow the HTML spec’s directions on custom attributes. That’s just me. You might want to let it all hang out. Fair play.

The data-ng-model attribute defines the model for the input as the title property of variable named item bound to the controller’s scope. Interestingly, this variable doesn’t exist at run-time. Instead of erroring with a warning about item being undefined Angular handles the creation of it once the form is submitted. This is a great example of how casual Angular is with their models. There’s no ceremony here. Add the data-ng-model attribute and the binding is done for you. If the variable exists in scope, the input is bound to it. If it doesn’t exist, Angular creates it. That’s pretty sweet.

The list attribute references the datalist element that the input should look for in order to build value hints. The value of the attribute is the id of the datalist you want to use.

The button at the end of the form is the next interesting piece of markup. On that input we’re using data-ng-click in order to bind the addItem function to click events on the form. Angular will look for addItem in the global namespace and in the controller’s scope. As you’ll see in the JavaScript example, we’re safely binding the method to the controller’s scope. If you click this button after adding a new title the title will be immediately added to the autocomplete list.

Finally, there’s the datalist element which uses data-ng-repeat to build out the list of autocomplete options. This directive is bound to the titles property of the controller scope. Since Angular models are bidirectional, once you update $scope.titles this list will be updated with new values. Pretty sweet. As you can see, this datalist element has the id we referenced earlier in the list attribute of the input.

<body data-ng-controller="MainCtrl">
    <form>
      <div>
        <label>title</label>
        <input type="text" data-ng-model="item.title" list="comicstitle">
      </div>
      <div>
        <input type="Button" value="Add" data-ng-click="addItem(item)">
      </div>
    </form>
  </div>
  <datalist id="comicstitle">
    <option  data-ng-repeat="ttl in titles" value="{{ttl}}"> 
  </datalist>
  </body>

Now that you’ve seen the markup, let’s look at the JavaScript that binds it all together. It’s actually pretty simple. Most of the wiring is done in the markup so the JavaScript is pretty sparse. That said, there are some interesting things so let’s look at it in-depth.

For starters we’re defining the starter list of titles as a variable on the controller’s scope. It’s a simple array bound to $scope.titles.

Have I mentioned that data binding is one of my favorite things about Angular? There’s absolutely no ceremony with creating a model. It’s just a variable on the controller’s scope (or even the global scope if you’re super lazy.)

After the model, the only other thing on the controller is a function called addItem.

This function is the method referenced in the data-ng-click attribute in the markup. It does a couple of things. The first is that is pushes the new value to $scope.titles. This example uses lodash’s contains function to test whether or not the title already exists in the array before it’s added. That keeps the list unique.

The next bit is another neat illustration of Angular models. Since the item we created when we added a title, setting it the object to an empty object is a shortcut to clearing the values out of the form. In this case there’s just the one form element, but if there were fifty or five hundred form elements with their data-ng-model attributes bound to properties of item this single line would clear all the values out of every bound form element.

app.controller( 'MainCtrl' , function( $scope ) {
  $scope.titles = [ "Action Comics" , "Detective Comics" , "Superman" , "Fantastic Four" , "Amazing Spider-Man" ];
  $scope.addItem = function( item ) { 
    if ( !_.contains( $scope.titles , item.title ) ){
      $scope.titles.push( item.title );
    } 
    $scope.item = {};
  }
});

Check it out, in action, with this plunker.


And there you have it. Let me know if you have any questions, comments or corrections in the comments.

Sure, It’s 5 Months Too Late for the Contest, BUT.. I Finally Did Something with the Hubway Data

hubway-static

The Hubway Data Visualization Challenge was a contest run using the data released by the local bike share. I really wanted to do something for the contest and even started a project in October to try to do something with it.

You know how that turned out

Anyway, between finding myself with a life that I can fill occasionally with fun programming and the need to do some cool visualizations for my presentation at HTML5 Developer Conference, I resurrected the project and managed to finish the thing over a weekend.

What did I do?

The image above shows a snapshot of the final output. What does it show?

  • The top ten Hubway departure points mapped against their top ten destinations.
  • Google is queried for each departure/arrival pair to create a “bike-friendly” route between the two points.
  • This is then plotted on the map using one color for each departure station. The size of the line is directly proportional to the number of trips for each departure and arrival pair.
  • Every line is 40% opaque, so the darker the path on a particular road, the more different trips pass over that road.
  • The bicycling layer is turned on, to show bicycle infrastructure. So basically, thick dark lines on top of a bike path = a good match for usage and infrastructure*.

*Theoretically. As the following table shows, the “bike friendly” route is not always the most direct. What I did here was calculate the correlation between the distance between the two latitude and longitude points in a straight line and the distance as calculated by the Google directions service. As you can see, some of the routes are 2x as far as the flying crow when approached on a bike-friendly route.

Boston Public Library – 700 Boylston St. The Esplanade – Beacon St. at Arlington St. 2.13
Boylston St. at Arlington St. Charles Circle – Charles St. at Cambridge St. 2.04
Boylston St. at Arlington St. TD Garden – Legends Way 1.98
Charles Circle – Charles St. at Cambridge St. TD Garden – Legends Way 1.93
Lewis Wharf – Atlantic Ave. Aquarium Station – 200 Atlantic Ave. 1.87
TD Garden – Legends Way Boylston St. at Arlington St. 1.85
Boston Public Library – 700 Boylston St. Charles Circle – Charles St. at Cambridge St. 1.83
Charles Circle – Charles St. at Cambridge St. Boston Public Library – 700 Boylston St. 1.83
Charles Circle – Charles St. at Cambridge St. The Esplanade – Beacon St. at Arlington St. 1.75
Charles Circle – Charles St. at Cambridge St. Stuart St. at Charles St. 1.74
Kenmore Square / Comm Ave Boylston St. at Arlington St. 1.66
Charles Circle – Charles St. at Cambridge St. Boylston St. at Arlington St. 1.57
Kenmore Square / Comm Ave Newbury St / Hereford St. 1.57
TD Garden – Legends Way Post Office Square 1.56
Kenmore Square / Comm Ave Charles Circle – Charles St. at Cambridge St. 1.51
Kenmore Square / Comm Ave The Esplanade – Beacon St. at Arlington St. 1.5
Boylston St. at Arlington St. Boston Public Library – 700 Boylston St. 1.49
Newbury St. / Hereford St. Charles Circle – Charles St. at Cambridge St. 1.47
Lewis Wharf – Atlantic Ave. TD Garden – Legends Way 1.46
Lewis Wharf – Atlantic Ave. Fanueil Hall – Union St. at North St. 1.46
Beacon St. / Mass Ave. Boylston St. at Arlington St. 1.45
Lewis Wharf – Atlantic Ave. Post Office Square 1.45
Newbury St. / Hereford St. Washington St. at Rutland St. Boston 1.45
South Station TD Garden 1.44
Newbury St. / Hereford St. Yawkey Way at Boylston St. 1.44
Back Bay / South End Station Prudential Center / Belvidere 1.43
Boylston St. at Arlington St. Kenmore Sq. /Comm Ave 1.42
Lewis Wharf – Atlantic Ave. South Station – 700 Atlantic Ave. 1.4
South Station Seaport Hotel 1.39
TD Garden – Legends Way South Station – 700 Atlantic Ave. 1.38
TD Garden – Legends Way Faneuil Hall – Union St. at North St. 1.38
Boylston St. at Arlington St. Mayor Thomas M. Menino – Government Center 1.38
Back Bay / South End Station Christian Science Plaza 1.38
Boston Public Library – 700 Boylston St. Fanueil Hall – Union St. at North St. 1.36
Back Bay / South End Station Boston Medical Center – 721 Mass. Ave 1.36
Lewis Wharf – Atlantic Ave. Rowes Wharf – Atlantic Ave 1.36
Boston Public Library – 700 Boylston St. Beacon St. / Mass Ave 1.35
Beacon St. / Mass Ave. Boston Public Library – 700 Boylston St. 1.35
Back Bay / South End Station Washington St. at Rutland St. Boston 1.34
Boston Public Library – 700 Boylston St. Tremont St. / West St. 1.33
Beacon St. / Mass Ave. TD Garden – Legends Way 1.33
TD Garden – Legends Way Aquarium Station – 200 Atlantic Ave. 1.32
South Station Cross St. at Hanover St. 1.31
South Station Boylston St. at Arlington St. 1.31
Newbury St. / Hereford St. Landmark Centre 1.31
TD Garden – Legends Way Summer St. / Arch St. 1.3
Newbury St. / Hereford St. Kenmore Sq / Comm Ave. 1.29
Charles Circle – Charles St. at Cambridge St. Beacon St / Mass Ave 1.28
Back Bay / South End Station Stuart St. at Charles St. 1.28
Beacon St. / Mass Ave. Charles Circle – Charles St. at Cambridge St. 1.28
South Station Boylston St. / Berkeley St. 1.27
Lewis Wharf – Atlantic Ave. Congress / Sleeper 1.26
Back Bay / South End Station Tremont St / W Newton St. 1.25
Lewis Wharf – Atlantic Ave. Seaport Square – Seaport Blvd. at Boston Wharf 1.25
Boston Public Library – 700 Boylston St. Stuart St. at Charles St. 1.22
Boylston St. at Arlington St. Rowes Wharf – Atlantic Ave. 1.22
Back Bay / South End Station Washington St. at Waltham St. 1.22
Charles Circle – Charles St. at Cambridge St. Kenmore Sq / Comm Ave 1.22
Beacon St. / Mass Ave. The Esplanade – Beacon St. at Arlington St. 1.22
Kenmore Square / Comm Ave Boston Public Library – 700 Boylston St. 1.21
South Station Boston Public Library – 700 Boylston St. 1.2
Boston Public Library – 700 Boylston St. Newbury St. / Hereford St. 1.19
Newbury St. / Hereford St. Boston Public Library – 700 Boylston St. 1.19
South Station Lewis Wharf – Atlantic Ave 1.17
South Station Aquarium Station – 200 Atlantic Ave. 1.17
TD Garden – Legends Way Rowes Wharf – Atlantic Ave. 1.17
Lewis Wharf – Atlantic Ave. Chinatown Gate Plaza – Surface Rd. at Beach St. 1.17
Charles Circle – Charles St. at Cambridge St. Cross St. at Hanover St. 1.16
TD Garden – Legends Way Congress/Sleeper 1.15
Newbury St. / Hereford St. Stuart St. at Charles St. 1.15
Boylston St. at Arlington St. Tremont St. / West St. 1.13
TD Garden – Legends Way Seaport Square – Seaport Blvd. at Boston Wharf 1.11
Back Bay / South End Station Columbus Ave. at Mass Ave. 1.11
Charles Circle – Charles St. at Cambridge St. Mayor Thomas M. Menino – Government Center 1.11
Beacon St. / Mass Ave. Boylston / Mass Ave 1.11
Newbury St. / Hereford St. Packard’s Corner – Comm Ave. at Brighton Ave. 1.11
Kenmore Square / Comm Ave Landmark Centre 1.09
South Station Rowes Wharf – Atlantic Ave. 1.08
Back Bay / South End Station Roxbury Crossing Station 1.07
Beacon St. / Mass Ave. MIT at Mass Ave / Amherst St. 1.07
Newbury St. / Hereford St. Boylston St. at Arlington St. 1.07
Boston Public Library – 700 Boylston St. South Station – 700 Atlantic Ave. 1.06
Boston Public Library – 700 Boylston St. Boylston St. at Arlington St. 1.06
Boylston St. at Arlington St. Newbury St. / Hereford St. 1.06
Boylston St. at Arlington St. South Station – 700 Atlantic Ave. 1.04
Beacon St. / Mass Ave. Christian Science Plaza 1.02
Beacon St. / Mass Ave. Kenmore Sq. / Comm Ave 1.01
Kenmore Square / Comm Ave Agganis Arena – 925 Comm. Ave. 1
Kenmore Square / Comm Ave B.U. Central – 725 Comm Ave 1
Kenmore Square / Comm Ave Packard’s Corner – Comm. Ave at Brighton Ave. 1

The intent is to highlight where bicycle infrastructure can be improved in the city- especially for the presumably inexperienced cyclists on Hubway machines.

I’ll follow up with a post on the technical implementation shortly. In the interim, check out the code on Github

Using the Geolocation API

The following is probably the last long-form article you’ll see here for a while. I’m in full on book mode for the next couple of months, so I’m not expecting to be writing a ton here for the foreseeable future.

Anyway, the following is actually inspired by the kind of work we’re going to be doing for the book. I’m not actually doing any code-heavy writing for the book, so I wanted to get my hands dirty with this sort of content. It’s fun.

As an aside, I’ll have another (short) post about the book shortly. I’ve got a full allotment of co-authors and I’d like to give them, and the project, a little shine before I turn into a writing hermit.

And now… Geolocation


One of the most powerful aspects of mobile web app development is the ability to blur the line between the real world and the world on the screen. Allowing users to interact with physical places in novel ways is driving startups across the world and is infiltrating some of the most popular sites and applications on the web. Facebook, Foursquare, Twitter, Google+ and countless other services have built the idea of location into the core of their applications. You too can do the same in your mobile web app by taking advantage of the well-supported Geolocation API.

Whether it’s interaction with your own location based services or with a third party API, like the Google Maps API in use in this recipe, the journey begins with getting the user’s latitude and longitude.

In this article you’ll learn how to:

  • Use the W3C Geolocation API to get a user’s latitude and longitude
  • Smoothly handle devices without Geolocation support, providing a reasonable fallback for older devices
  • Use the Google Maps API to place a marker indicating the user’s location, labeled with a friendly place name

The Basics of the Geolocation API

Before we dive into the heart of the example let’s quickly look at the Geolocation API.

The elevator pitch is to the point- the W3C’s Geolocation API allows developers to retrieve the geographical location of a device. It became a Candidate Recommendation, the level at which the W3C deems features and functionality pretty much settled, in September of 2010 and already has support across a variety of devices and browsers. At the present time it’s supported all the major smartphone browsers and even on the desktop it’s supported by all major browsers except Internet Explorer 6, 7, 8 and Safari 3.2 and 4.0.

As a note, the Geolocation API was heavily influenced by the analogous functionality provided by the Google Gears plugin. This is why you often see the mothballed Google Gears plugin referenced as a fallback in many geolocation examples.

The API itself is straightforward. It provides a navigator.geolocation object which in turn provides two methods (watchPosition and getCurrentPosition) which allow the browser to query the device’s location through the use of location information servers. If you’re getting a location for use in a search or in a check-in, then getCurrentPosition is the method you want to use as it’s designed for a single location lookup. If you’re tracking a user’s location over time, then watchPosition is the way to go since it’s designed to be used over a longer period of time.

Location information is pulled from a variety of sources including IP address, device GPS, Wi-Fi and Bluetooth MAC address, RFID, or Wi-Fi connection location. The different level of precision inherent in these several methods is exposed by the API as an accuracy property.

Now that we’ve taken a look at the Geolocation API, let’s walk through our code in depth.

Getting Started with the Geolocation API

While the Geolocation API is straightforward, getting it up and running smoothly in the real world is a little bit tricky. Accounting for a successful result is one thing, making sure there’s a decent response for browsers without geolocation capability or in other instances where geolocation isn’t available is another.

Our example will touch on ways to minimize these issues and will illustrate the basics of a successful request.

Testing for the Geolocation object and Querying the User’s Location

The first thing you’ll need to do when working with Geolocation is to test whether or not it’s actually available in the browser. This is done by testing against the presence of the navigator.geolocation object. As you’ll see in the following code sample, this is a simple if…else block with a call to the navigator.getCurrentPosition() method when the object is present and a fallback when it’s not available.

The method getCurrentPosition() takes three arguments:

  1. the function to run on a successful location request
  2. the function to run as when the request fails
  3. a PositionOptions object containing other optional configuration objects.

In our case we’re passing in two named functions, success and failure, and an optional timeout of five seconds, which will keep things moving if something goes awry with the request.

Listing 1 Testing for the presence of the navigator.geolocation object

if (navigator.geolocation){  // does the geolocation object exist?             
  navigator.geolocation.getCurrentPosition( 
    success, 
    failure, 
    {timeout:5000} 
  );          
} else {
  failure();  
}      

In addition to the timeout seen in the previous example the PositionOptions object accepts two other options- enableHighAccuracy and maximumAge. enableHighAccuracy indicates that you would like receive the best possible results at the potential cost of speed or battery performance. maximumAge sets a limit, in milliseconds, for the age of a cached position object. The browser caches recent position location responses. Setting maximumAge to 0 will immediately force the browsers to try to obtain a new location.

Handling a successful geolocation request

The first function we’ll look at it is our success function. You can see it in Listin 1.2

In our example we’re using the Google Maps API to display a marker with the user’s current location.

The function accepts a single data argument. This argument is automatically passed into the function by the geolocation API. This is object is defined in the specification to contain two properties coords and timestamp. timestamp is, as expected, a timestamp indicating the age of the position information. For this example we’re most interested in the coords object which contains a latitude/longitude pair indicating the user’s position.

Moving on from the single argument, you’ll see several Google Maps specific variables.

The first, GM,represents a simple technique to speed up JavaScript. By creating a local representation of the google.maps object we save lookups to the global space. In general, local variables are faster. This is especially important with mobile which devices don’t have the fastest JavaScript engines.

Every little bit helps.

The most important piece, from a geolocation perspective, is the use of two properties, data.coords.latitude and data.coords.longitude, to build a new Google Maps LatLng object. The LatLng object is a core component of Google Maps. At its core it’s a latitude/longitude pair enhanced with methods and properties used throughout the API. To create one in our example you simply pass it the two properties of the data.coords object. We store that in our position variable.

We now have our user’s location, ready to place on the map.

The next section we’re using the Google Maps Geocoder to get a friendly label for the user’s location. Geocoding works in two ways. Normal geocoding means you pass the service an address string and it will return a series of geographical results. In our case we’re doing reverse geocoding, which means we pass the service a latitude/longitude pair and the service returns whatever it knows about the location.

Listing 2 Successfully handling a geolocation request

var success = function( data ){ //the data object, passed into success
  var GM = google.maps,
      mapOptions = {
        zoom: 12,
        center:  defaultPosition,
        mapTypeId:  GM.MapTypeId.ROADMAP
      },
      map = new GM.Map(  document.getElementById('map'), mapOptions),
      position = new GM.LatLng( 
        data.coords.latitude, //accessing the coords property
        data.coords.longitude 
      ),      
      niceAddress = &amp;quot;Your location&amp;quot;,
      geocoder = new GM.Geocoder();
      geocoder.geocode( { 'latLng' : position  }, 
        function( results, status ) {
          if ( status == GM.GeocoderStatus.OK ) {
            if (results[0]) {
              niceAddress = results[0].formatted_address;
            }
          } 
          var infowindow = new GM.InfoWindow({
            map: map,
            position: position,
            content: niceAddress
          });
        });
      map.setCenter(position);
    }
  
  

Handling a Geolocation Failure

Our failure function handles two negative situations. If the user doesn’t have a geolocation enabled browser or if there’s an error in the geolocation lookup, this function is ready to step in and save the day. The failure function can be seen in Listing 3

You’ll see the setup is similar to the success function with a Google Maps object being created with some smart defaults.

The major difference is in the way we get the latitude and longitude for the map. Instead of getting the coordinates from a geolocation response we create a simple form to allow the user to enter their location. Inside the formResponse function we use then use the Google Maps Geocoding service to get a latitude and longitude pair corresponding to the location in the form submission.

Additionally we use the geolocation error response, if it exists, to build out a slightly more useful error message. If a browser supports geolocation and has some issue with the location request it should return an error response as the single argument to the provided callback function.

Listing 3 The failure Function

var failure = function( error ){ //The potential error response
  var  GM = google.maps,
       mapOptions = {
         zoom: 12,
         center:  defaultPosition,
         mapTypeId:  GM.MapTypeId.ROADMAP
       },
       map = new GM.Map(  document.getElementById('map'), mapOptions),
       formResponse = function(e){
         var geocoder = new GM.Geocoder(),
             position = defaultPosition,
             niceAddress =  &amp;quot;Sorry We Couldn't Find Your Location&amp;quot;;
         geocoder.geocode(
           { 'address':  document.getElementById(&amp;quot;location&amp;quot;).value }, 
           function( results, status ) {
             if ( status ==  GM.GeocoderStatus.OK ) {
               if (results[0]) {
                 niceAddress =  results[0].formatted_address;
                 position = new GM.LatLng( 
                   results[0].geometry.location.lat(),
                   results[0].geometry.location.lng() 
                 )
               }
             } 
           var options = {
             map : map,
             position :  position,
             content :  niceAddress
            },
           infowindow = new  google.maps.InfoWindow(options);
           map.setCenter(options.position);
           document.getElementById(&amp;quot;geocode&amp;quot;).style.display=&amp;quot;none&amp;quot;;
         }
       )
     return false;
   }
   var  fallback = document.createElement(&amp;quot;form&amp;quot;);
   fallback.id=&amp;quot;geocode&amp;quot;;
   if ( error ) { 
     switch(error.code) {//Error Handling based on error.code   
      //HANDLE ERRORS//
   }      
 }  
 fallback.innerHTML  = &amp;quot;&amp;lt;label for='location'&amp;gt;Eneter Your Location&amp;quot; + 
  &amp;quot;&amp;lt;input  type='text' id='location' /&amp;gt;&amp;lt;/label&amp;gt;&amp;lt;input type='submit'  /&amp;gt;&amp;quot;;
 fallback.onsubmit  = formResponse;
 document.getElementById(&amp;quot;main&amp;quot;).appendChild(  fallback );
};

The error response contains a code object indicating the type of error and a human readable message string that’s defined to be used in debugging or for error logs. There are four potential values for the error.code. These can be seen in Table 1:

Table 1 Possible error response codes

Code

Name

Definition

0

UNKNOWN_ERROR

The location lookup failed due to an undefined error

1

PERMISSION_DENIED

The location lookup failed because the application does not have permission to use the Geolocation API.

2

POSITION_UNAVAILABLE

The position of the device could not be determined.

3

TIMEOUT

The location lookup took longer than the length of time defined in

Putting it all together

Listing 4 shows the completed function. In it we’ve wrapped our two methods into a larger function called loadMap. Doing so allows us to streamline the code by creating a single set of defaults for the map and to encapsulate all of the functionality under a single function so as to keep the global namespace as neat as possible.

Listing 4 The Completed Function

var loadMap = function(){
  var GM = google.maps, 
      defaultPosition = new GM.LatLng(42, -71),
      mapOptions = {
      zoom: 12, 
      center: defaultPosition, 
      mapTypeId: GM.MapTypeId.ROADMAP},
    map = new GM.Map( 
      document.getElementById('map'), 
      mapOptions
    ), 
    success = function( data ){
      var position = new GM.LatLng( 
        data.coords.latitude, 
        data.coords.longitude 
      ), 
         niceAddress = 'Your location',
         geocoder = new GM.Geocoder();
      geocoder.geocode({ 
        'latLng': position },
         function( results, status ) {
           if ( status == GM.GeocoderStatus.OK ){
             if (results[0]) {
               niceAddress = results[0].formatted_address; 
             }
           }
           var infowindow = new GM.InfoWindow({
             map: map, 
             position: position, 
             content: niceAddress
           });
         }
       );
      map.setCenter( position );
    }, 
    failure = function( error ){ 
      var formResponse = function(){ 
        var geocoder = new GM.Geocoder(),
            position = defaultPosition, 
            niceAddress = 'Sorry We Couldn't Find Your Location';
        geocoder.geocode({
     'address':document.getElementById('location').value 
    }, 
          function( results, status ) {
            if ( status == GM.GeocoderStatus.OK ){ 
              if (results[0]) {
                niceAddress = results[0].formatted_address; 
                position = new GM.LatLng(
                  results[0].geometry.location.lat(),
                  results[0].geometry.location.lng() 
                )
               } 
             }
             var options = {
                map: map, 
                position: position, 
                content: niceAddress
              },
              infowindow = new google.maps.InfoWindow(options);
              map.setCenter(options.position);
              document.getElementById('geocode').style.display='none';
           }
         )
       return false; 
     } 
     var fallback = document.createElement('form');
     fallback.id='geocode';
      if ( error ) {
       switch(error.code) {
         case error.PERMISSION_DENIED:
     	    fallback.innerHTML += &amp;quot;&amp;lt;p&amp;gt;You chose not share geolocation data. Please, use the form below. &amp;lt;/p&amp;gt;&amp;quot; ;  
         break;  
	 case error.POSITION_UNAVAILABLE:
            fallback.innerHTML += &amp;quot;&amp;lt;p&amp;gt;Sorry, we couldn't determine your location. Please, use the form below. &amp;lt;/p&amp;gt;&amp;quot; ;
         break;  
         case error.TIMEOUT: 
            fallback.innerHTML += &amp;quot;&amp;lt;p&amp;gt;Sorry, the location request time out. Please, use the form below. &amp;lt;/p&amp;gt;&amp;quot; ;
         break;  
         default: 
            fallback.innerHTML += &amp;quot;&amp;lt;p&amp;gt;Sorry, there was an error. Please use the form below. &amp;lt;/p&amp;gt;&amp;quot; ;
         break;
       }  	
    }
    fallback.innerHTML += &amp;quot;&amp;lt;label for='location'&amp;gt;Eneter Your Location &amp;lt;input type='text' id='location' /&amp;gt;&amp;lt;/label&amp;gt;&amp;lt;input type='submit' /&amp;gt;&amp;quot;;
    fallback.onsubmit = formResponse;
    document.getElementById(&amp;quot;main&amp;quot;).appendChild( fallback );
  };
  if (navigator.geolocation){
    navigator.geolocation.getCurrentPosition( success, failure, {timeout:5000} ) ;			
  } else {
    failure();	
  } 	
}

Summary

With that we’ve walked through the basics of the geolocation API. While our example utilizes the Google Maps API, any exploration of location based services can be based on the same pattern. Use the navigator.geolocation object where available, and then design in a simple fallback for non-supporting browsers and devices.

code used in the geolocation sample

Recent Reading (JS Natives Duke it Out, Regexp in jQuery, Performance, a New Image Format?)

Sorry, it’s been a while. I’ve been busy at work, I’ve been wringing every last bit out of summer on my bike, and I’ve spent a lot of my free time on my upcoming CSS presentation, so I haven’t been posting as much as I would like. Fall is here. Which means I should have more time for writing. That’s cool.

Anyway, to break the ice here are a few articles that have recently caught my attention.
Continue reading “Recent Reading (JS Natives Duke it Out, Regexp in jQuery, Performance, a New Image Format?)”