Extending Google Maps class with RequireJS and Underscore

24th January, 2015 - Posted by david

I’ve been working alot with RequireJS lately and love the whole modular JS movement, where blocks of code are safely contained within separate functions, that can be loaded in when necessary, without worrying about variable/function name clashes. Generally each module is stored in it’s own file but you can combine several modules into one giant file, in theory containing your entire JS code for your site!

When working with Google Maps, I was thinking it’d be nice to simply extend the google.maps.Map class and add in my own functions, e.g. showLoading to display a nice, semi-transparent ‘Loading…’ div in the centre of the map while the code does an AJAX request to get some data. So, you’d end up with something like:

1
2
var map = new Map();
map.showLoading();

So, first thing you need to do is add the Asynch plugin for requireJS to your requireJS config. For external plugins, I tend to source them from https://cdnjs.com/ if possible, cutting down on some of my own server bandwidth. I have my RequireJS config in my require.min.js file, after the main block of minified code. Here’s an edited version, with the asynch plugin on line 6:

1
2
3
4
5
6
7
8
9
10
11
12
13
requirejs.config({
        'baseUrl': '//cdn.mydomain.com/js',
        'paths': {
        'jquery': 'libs/jquery.min',
        'underscore': 'libs/underscore.min',
        'async': '//cdnjs.cloudflare.com/ajax/libs/requirejs-async/0.1.1/async'
    },
    'shim': {
        'underscore': {
            exports: '_'
        },
    }
});

Next up, we have a map.js file, which, via the define function, we’ll pass UnderscoreJS and the Google Maps JS code using the Asynch plugin. This file is going to return a Map object, which we can then use to instantiate new instances of the class. The file starts off with a Map function, which takes as a parameter the id of the div you want to put the map into and the latitude & longitude of the map’s centre. Obviously you could pass more parameters if you wish, e.g. initial zoom level etc. This Map function will then create a variable of type google.maps.Map, set the map up and return the variable as output from the function. This way when you do var myMap = new Map();, your myMap will be able to call all of google.maps.Map‘s functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
define(
    ['underscore', 'async!https://maps.googleapis.com/maps/api/js?sensor=false'],
        function(_) {

            var Map = function (div, lat, lng) {
                zoom: 15,
                center: new google.maps.LatLng(lat, lng)
            });

            return map;
        }

        // more code below to follow...

        return Map;
});

We also need to give our Map‘s protoype the google.maps.Map prototype, which is simply:

1
Map.prototype = google.maps.Map.prototype;

Finally, if we want to add our own functions to our Map, we use UnderscoreJS to extend the Map.prototype with our functions. So, for example, I have one called isBigDrag to detect if the user has dragged the map a reasonable amount to fetch more data, as well as showLoading and hideLoading to show/hide my nice ‘Loading…’ div.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
_.extend(Map.prototype, {
    isBigDrag: function (prevCentre) {
        var currentCentre = this.getCenter();
        // .. etc
    },

    showLoading: function() {
        // etc.
    },

    hideLoading: function() {
        // etc.
    }
});

See below for the entire file, which is stored in ‘classes/map.js’ in baseUrl above.

To pass this new Map class into a script and to be able to do var myMap = new Map();, we do so via a call to requirejs in the script, as follows:

1
2
3
4
5
6
7
requirejs([
    'underscore', 'classes/map',
], function(_, Map) {
    var myMap = new Map('id', 50.0, 10.0);
    myMap.showLoading();
    // etc.
});

Took me a while to figure it all out but works pretty well now. The classes/maps.js file in it’s entirety is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
define(
    ['underscore', 'async!https://maps.googleapis.com/maps/api/js?sensor=false'],
    function(_) {

        var Map = function (div, lat, lng) {
            zoom: 15,
            center: new google.maps.LatLng(lat, lng)
        });

        return map;
    }

    Map.prototype = google.maps.Map.prototype;

    _.extend(Map.prototype, {
        isBigDrag: function (prevCentre) {
        var currentCentre = this.getCenter();
            // .. etc
        },

        showLoading: function() {
            // etc.
        },

        hideLoading: function() {
            // etc.
        }
    });

    return Map;

});

Read more...

Safari and long lines in Google Maps Javascript API Directions Service

11th October, 2012 - Posted by david

I’ve been working on building the mobile version of CarsIreland.ie lately and one of the features we want is to offer directions from a user’s current location to any of our car dealerships, assuming we have their latitude and longitude co-ordinates. Getting the directions and displaying them on a map are ridiculously simple, thanks to Google’s Direction Service API. Going into the detail of getting this working is beyond the scope of this post, but what I hope to show here is how to resolve an issue in mobile (and possibly desktop) Safari, and maybe even some other browsers.

When you have instantiated your new google.maps.DirectionsService() and called it’s route function, passing your source and destination (among other things) as parameters, you’re supposed to give a callback function with response and status parameters. Assuming the call to route was successful, the response parameter should be a big JSON block of at least one route and step by step guides detailing how to get from source to destination. Conveniently, Google also provide a very handy object called a DirectionsRenderer, which has a function called setDirections, which can generate a nicely formatted HTML table of directions. See https://google-developers.appspot.com/maps/documentation/javascript/examples/directions-panel for an example.

The problem I experienced and am aiming to solve here is that some of the directions (in Ireland at least) involve very long motorway/freeway names, where each junction is separated by a ‘/’, but with no spaces. This can lead to very long strings of the format ‘Long/And/Winding/Junction/Name’. Add in the fact that they also include the Irish/Gaelic translation for some words and it gets even longer! When viewing this steps on Android’s Dolphin or even a desktop Firefox, the browser recognizes that it can split the line at the ‘/’ and thus doesn’t widen the page. Safari unfortunately doesn’t do this and forces very wide page widths, which makes the page look awful. So, today I figured a way to resolve this.

What I did was traverse the response object you get back from Google, looking at each instruction step, trying to find one of these long strings, and replacing the ‘/’ with ‘ / ‘, i.e. a space character on either side, so Safari will then break the lines and not force a wide page. Doing a simple string replace wasn’t sufficient, as some of the instructions contain HTML tags, which can have ‘/’ in them that we ideally want to keep.

So first up is the Javascript regular expression to find matching instances of “long/string”. In simple terms, it’s any alpha-numeric character, followed by a ‘/’, followed by another alpha-numeric character. In Javascript I came up with:

1
var patt = /([a-z0-9]{1})\/([a-z0-9]{1})/gi;

The gi at the end of the pattern means global search (i.e. don’t stop at the first match), case insensitive (hence no ‘A-Z’ is required).

Now, all we have to do is cycle through the response, looking for routes, legs, steps and instructions and replacing as necessary, via a string’s replace function:

1
2
3
// e.g.
var repl = "$1 / $2";
instruction = instruction.replace(patt, repl);

So, to loop through the response and do the replacing, we need a few nested for loops, our pattern and our replace sequence, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
if (typeof response['routes'] != 'undefined') {
    var patt = /([a-z0-9]{1})\/([a-z0-9]{1})/gi;
    var repl = "$1 / $2";
    // cycle through each route
    for (var i=0; i<response['routes'].length; i++) {
        var route = response['routes'][i];
        if (typeof route['legs'] != 'undefined') {
            // cycle through each leg in that route
            for (var j=0; j<route['legs'].length; j++) {
                var leg = route['legs'][j];
                if (typeof leg['steps'] != 'undefined') {
                    // cycle through each step in that leg
                    for (var k=0; k<leg['steps'].length; k++) {
                        var instructions = leg['steps'][k]['instructions'];
                        // if we've found an instruction with a matching pattern
                       if (instructions.match(patt)) {
                           // do the replace
                           response['routes'][i]['legs'][j]['steps'][k]['instructions'] = instructions.replace(patt, repl);
                       }
                   }
               }
           }
       }
   }
}

So, hopefully this well help people experiencing the same problem I was having with the long strings caused by a lack of spaces between ‘/’ characters! As an alternative, one may just wish to only have a space after the ‘/’, in which case, the replace pattern becomes "$1/ $2".

Read more...

Google Maps Zoom Control missing in Internet Explorer (IE): a solution

23rd February, 2011 - Posted by david

When I was working on rebuilding daft.ie’s mapping system for the modern web, it involved writing alot of Javascript (JS) code and loading this via a couple of JS source files, one external one from Google (i.e. the core Google Maps JS) and another from our own web-servers.

It all worked fine in most browsers, except of course Internet Explorer (IE). To be fair, IE8 was fine; however, seemingly at random, the zoom control would sometimes not appear on the map for IE7 and IE6 and you’d get one of IE’s very unhelpful error messages. At Daft we like to ensure that the site is available and consistent on all browsers, so it was imperative to get this issue sorted.

Because of the aforementioned unhelpful error messages, it proved to be a very difficult bug to nail down. I began to suspect that it was due to the code from Google loading after the map is set-up in the daft.ie hosted file, i.e. IE was calling Google-specific functions before it had the code. As it was all wrapped in a jQuery $(document).ready I didn’t think this could happen, but turns out that in the older versions of IE it could! I tried moving the JS around, loading the Daft-specific JS as late as possible, to try and give the Google JS the maximum amount of time to load, but that was to no avail.

So, I needed to find a way to load the Daft JS when I was 100% certain the Google JS had arrived. After various futile attempts, the solution ended up being pretty simple – change my

1
$(document).ready(function() {

to

1
$(window).bind("load", function() {

That way, the bulk of my JS is only executed once everything else has loaded and sure enough, the issue disappeared!

Read more...

HTML Div’s layered onto Google Streetview: Chrome issue

16th December, 2010 - Posted by david

While working on Daft‘s Map Search facility, we naturally decided to integrate Google’s Street View (SV) as a feature, especially since we were one of the launch partners for the product in Ireland. We decided to do this as follows:

  • For each property icon on the map, when you click on it, bring up a small info window with a picture, minimal details, a link to the property’s main page and, where applicable a link to the SV image for that property
  • When clicking on the SV link, the entire map is replaced with the SV window
  • A button would then exist to close the SV window and revert back to the map

Read more...

Google Maps API V3 Overview Control

9th October, 2010 - Posted by david

In mid-2009, Google launched version 3 of their fantastic Google Maps API. This new version was a complete overhaul of the existing system, moving to an MVC-based architecture, with the primary focus on speeding up the code so it would work better on mobile devices. It has now taken over from Maps API v2 as their primary Maps AP v2 now deprecated.

One of the things missing from v3 that was in v2 was v2’s GOverviewControl, which was the mini-map you’d see on the bottom right of the main map, which would be zoomed out further than the main map. When I started work on overhauling Daft.ie’s mapping system, we originally used Maps v2 and had such a control in place. We decided to use Maps v3 for the new system and it was then I noticed that the Overview Control was missing. At the time I was still learning how to use the API and have since become reasonably proficient in it, enough so to complete our maps overhaul! Taking these skills, I’ve now written a jQuery-based JavaScript extension that enables users to add the Overview Control to their map, along with a number of customisation options.

Read more...