KnockoutJS: multiple jquery actions in one binding handler

5th August, 2016 - Posted by david

A general rule I follow when using KnockoutJS is that there should be no DOM manipulation in the viewModel. The viewModel should be completely independent of any markup, while any changes to the DOM (via jQuery or otherwise) should be handled in the binding handler. This makes your viewModels much more portable and testable.

As I’m sure you’re aware if you’re reading this article(!), KnockoutJS’s binding handlers are applied to an element and have init and update functions that get called when a certain value in your viewModel changes. Within your init function, you can set up various DOM-element-specific jQuery handlers, while within your update function you can perform various DOM manipulations, trigger events etc., as well as reading from/updating your viewModel and much more.

A common situation I’ve come across a number of times is: say you have a big div with plenty of buttons and links that are tied into external jQuery plugins and DOM elements and you want to perform certain actions when they’re clicked or when other changes happen in your viewModel. You don’t really want to have loads of binding handlers for each separate change that might happen in your viewModel, your codebase could get quite big quite quickly. What I’m about to propose is a structure of how to apply 1 binding handler to the entire div, then call various functions to manipulate the DOM outside of your update binding handler function, via the viewModel.

So, I’ll start with the viewModel. I’m going to have an observable action attribute and 2 functions linkClicked and buttonClicked. (Please bear in mind, this is a very simple example for illustration purposes, you wouldn’t really call viewModel functions linkClicked etc.!) There’ll also be a resetAction function, which will be explained shortly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
exampleViewModel = (function($) {
    var viewModel = function() {
        this.action = ko.observable('');
    };
    viewModel.prototype.resetAction = function() {
        this.action('');
    };
    viewModel.prototype.linkClicked = function() {
        this.action('jQLinkClicked'); // prepended "jQ" to the function name to help the reader later
    };
    viewModel.prototype.buttonClicked = function() {
        this.action('jQButtonClicked');
    };
    //JS Module Pattern
    return viewModel;
}(jQuery));

ko.applyBindings(new exampleViewModel(), $('#example')[0]);

And now our HTML markup:

1
2
3
4
<div id="example" data-bind="exampleBindingHandler: action()">
    <a href="void(0)" data-bind="click: linkClicked"><br />
    <button data-bind="click: buttonClicked">
</div>

So now we can see that whenever we click either the link or the button, our action attribute will be updated and thus trigger the update function in the exampleBindingHandler binding handler that’s applied to the div. Let’s look at that binding handler now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ko.bindingHandlers.exampleBindingHandler = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        // do whatever initial set up you need to do here, e.g.
        $('body').addClass('whatever');
    },
    update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        // so this will be called whenever our observable 'action' changes

        // get the value
        var action = valueAccessor();
        // reset to empty
        viewModel.resetAction();

        switch (action) {
            case 'jQLinkClicked':
                alert('link');
            break;
            case 'jQButtonClicked':
                alert('button');
            break;
        }
    }
};

So you can see from the above how we can move from various different viewModel changes out to the binding handler and maniuplate the DOM in there. We read and save action from the valueAccessor, then reset it via the viewModel’s resetAction function, just to keep things clean.

At this point we have very simple alerts for each of our actions but of course in real life you’ll want to call your jQuery plugins, change the DOM etc. To keep things clean what we can do is have a simple JSON object with functions for each of the actions and within those functions do our heavy jQuery lifting, something along the lines of:

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
33
34
var _ = {
    jQLinkClicked: function() {
        // e.g.
        $('.class').parent().remove();
    },
    jQButtonClicked: function() {
        // e.g.
        $.plugin.foo();
    }
}

ko.bindingHandlers.exampleBindingHandler = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        // do whatever initial set up you need to do here, e.g.
        $('body').addClass('whatever');
    },
    update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        // so this will be called whenever our observable 'action' changes

        // get the value
        var action = valueAccessor();
        // reset to empty
        viewModel.resetAction();

        switch (action) {
            case 'jQLinkClicked':
                _.jQLinkClicked();
            break;
            case 'jQButtonClicked':
                _.jQButtonCliked();
            break;
        }
    }
};

So, in summary:

  • Have an observable action attribute in your viewModel
  • Apply the binding handler to your main div, with the observable action variable as the valueAccessor parameter
  • Set action accordingly in your viewModel
  • In your binding handler, figure out what jQuery/DOM manipulation etc. you want to do based on the value of action

Read more...

Autocomplete with KnockoutJS

3rd February, 2016 - Posted by david

Recently I started a new job at a company that is looking to transition away from a customised, unstructured, jQuery module set up to use KnockoutJS and RequireJS for it’s modules. This approach was chosen because the core platform is based on Magento and the forthcoming Megento 2 uses KnockoutJS heavily throughout it’s frontend templates. As a good starting point and proof of concept, we decided to look at converting our existing custom-autocomplete module from a combination of EJS and jQuery to pure KnockoutJS. Luckily for me, I was the one who got to implement it, and thus learn a new skill!

I’m not going to go into the ins and outs of how KnockoutJS works but in short it’s a MVVM system, where you have Models, Views and ViewModels, the latter being the interface between the other 2, the client and the server. This autocomplete was a standard input field, whereby on typing 3 characters, an AJAX call is made to the server looking for strings that matched the search string and displayed a clickable list of results underneath the input field. Additionally, you could use the arrow keys to select items in the menu, as well as the mouse. We also have different instances of the autocomplete, to search for different types of entities (e.g. searching for a product vs. search for a place), so we need the code to work with each.

From this point on I’m going to assume at least a basic knowledge of KnockoutJS, how it uses data-bind etc.

The View Model

So, first up we’ll want an Autocomplete viewModel, to handle the DOM events in the view (e.g. keyup etc.), fetch data from the server and call the correct model to format the received data. It’ll have 2 observable attibutes: suggestions, an array of suggestion objects, and q, the incoming query from the user. As a parameter we’ll pass it the model type to format the suggestions (e.g. LocationSuggestion below) and we’ll have functions to fetch suggestions as JSON from the server (loadSuggestions), add them to our suggestions array (addSuggestion, formatting the data via the model along the way) and clear our array (clearSuggestions), as well a helper function to look for valid character key presses (validKey). None of this is overly complex and it’s well commented, so I’ll just leave the whole class here:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
* AutoComplete viewModel. Handles the observable events from the view, requests data from the server and calls
* the corresponding Model above to format fetched data
*
* @param  options  JSON object of options, to contain:
*                  - url: URL to request the search results from
*                  - suggestionEntry: required model (i.e. one of the above) to format the data
*/

function AutoComplete(options) {

    // KnockoutJS standard is to refer to 'self' instead of 'this' throughout the class.
    // It's because 'this' in a sub-function refers to the function, not the viewModel
    var self = this;

    $.extend(self, options);

        // Array to store suggestions received from the server
        self.suggestions = ko.observableArray([]);

        // Value of input field that user queries
        self.q = ko.observable('');

        // Attribute to store the current AJAX request. Means we can cancel the current request if the observable 'q' changes
        self.ajaxRequest = null;

        /**
        * Append a JSON search result to our suggestions array. Instantiates the correct model to format the data
        * (view is rendered automatically by KnockoutJS)
        *
        * @param  suggestion  JSON object, returned from search server
        */

        self.addSuggestion = function (suggestion) {
        self.suggestions.push(new self.suggestionEntry(suggestion, self.q()));
    }

    /**
    * If the user has entered a valid search string (more than 3 latin-ish or punctuation characters), cancel the current AJAX request (if any),
    * fetch the data from the server, format it and store in 'suggestions' array
    *
    * @param  obj  HTML <input> element (not used)
    * @param  event  The event object for the triggered event (keydown)
    */

    self.loadSuggestions = function(obj, event) {
        // if a valid, non-control, character has been typed
        if (self.validKey(event)) {
            self.clearAjaxRequest(); // cancel current request
            var q = self.q();
            // if they've entered less than 3 characters, just clear the array, which clears the suggestions drop down
            if (q.length < 3) {
                self.clearSuggestions();
                return;
            }

            // request data from the server
            self.ajaxRequest = $.getJSON(self.url, {term: q}, function(response) {
                self.clearSuggestions(); // clear out current values
                for (var i = 0; i < response['suggestions'].length; i++) {
                    self.addSuggestion(response['suggestions'][i]); // add search result
                }
            });
        }
    }

    self.clearSuggestions = function() {
        self.suggestions([]);;
    }

    self.clearAjaxRequest = function() {
        if (self.ajaxRequest) {
            self.ajaxRequest.abort();
            self.ajaxRequest = null;
        }
    }

    /**
     * Check what key was pressed is valid: if it was alphanumeric, space, punctuation or backspace/delete
     */

    self.validKey = function(event) {
        var keyCode = event.keyCode ? event.keyCode : event.which;
        // 8 is backspace, 46 is delete
        return keyCode == 8 || keyCode == 46 || /^[a-zA-Z0-9\s\-_\+=!"£$%^&*\(\)\[\]\{\}:;@'#~<>,\.\/\?ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž]$/.test(event.key);
    }
}

We also store the current AJAX request with the object in the ajaxRequest attribute. By doing this, we can cancel any existing requests as the user types more keys. So, when they type the first 3 characters, a request is fired off; when they type the 4th character, we’ll cancel the existing request if it hasn’t finished and do a new search for the longer string.

The Model(s)

For this example, I mentioned above that the user could be searching for locations or products; let’s go with a location search for this example. Below, we have a class LocationSearch, which takes a JSON object that was returned from our server, formats the matched string by wrapping <strong> tags around the bit of the string that was matched (via the global accentInsensitiveRegex function, which I unfortunately don’t have the code for), generates the URL for the result and translates the type of location found (.e.g city, county etc.).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Model for a location search result. Formats data to be displayed in the HTML view
*
* @param  data  JSON object
* @param  q     User's original query
*
*/

function LocationSuggestionEntry(data, q) {
    this.type = (data.type === 'region' && Number(data.id) > 999) ? 'country' : data.type;

    var separator = data.url.indexOf('?') !== -1 ? '&' : '?';
    this.url = data.url + separator + 'autocomplete=1&ac-text=' + q;

    this.id = data.id;

    this.label = data.label;
    // wrap what the user typed in a <strong> tag
    var regexp = new RegExp('(' + accentInsensitiveRegex(q) + ')', 'gi');
    this.labelFormatted = data.label.replace(regexp, '<strong>$1</strong>');

    this.typeTranslated = Translator.translate('autocomplete::type::' + this.type);
}

The View

So, for the HTML side, we need an <input> field for the user’s query and a <ul> for the search results. The <ul> will obviously be hidden if we’ve no results to show. We wrap the whole thing in a <div> with class autocomplete, which we’ll use when binding the whole thing together later.

For the <input> field, we bind our AutoComplete‘s q attribute to Knockout’s textInput data binding (textInput: q), so that every time the value of the <input> changes, q will too. Additionally, we want to fire our loadSuggestions function, which will check the length of q and fetch suggestions from the server if it’s greater than 3 characters; this is achieved by calling loadSuggestions when a Javascript keyup event is fired on the <input> (event: {keyup: loadSuggestions}).

The HTML for the <ul> is also fairly straight-forward. If we have any suggestions to show, we want to add the has-results class to the <ul> (css: {'has-results': suggestions().length > 0}) and of course hide the <ul> when there’s less than 3 characters typed in the <input> (visible: q().length > 2). Assuming we have suggestions to show, we loop through the suggestions array, displaying an <li> for each, containing the suggestion’s labelFormatted and translatedType, as well as adding some attributes to the surrounding <a> (data-bind="attr: {href: url ...).

1
2
3
4
5
6
7
8
9
10
11
12
<div class="autocomplete">
    <input class="search-input" data-bind="textInput: q, event: {keyup: loadSuggestions}" type="text" name="text" placeholder="Enter a location..." />
    <ul class="autocomplete-results" data-bind="css: {'has-results': suggestions().length > 0}, visible: q().length > 2">
        <!-- ko foreach: suggestions -->
        <li>
            <a data-bind="attr: {href: url, 'data-type': type, 'data-id': id, 'data-label': label}">
                <span data-bind="html: labelFormatted"></span> - <span data-bind="text: translatedType"></span>
            </a>
        </li>
        <!-- /ko -->
    </ul>
</div>

Binding It All Together

At this point we need to bind our AutoComplete object to our div.autocomplete, passing it the search URL and suggestion type as parameters:

1
2
3
4
5
6
$('.autocomplete').each(function() {
    ko.applyBindings(new AutoComplete({
        url: '/suggestions/locations/',
        suggestion: LocationSuggestion
    }), this);
});

One last bit…

To get all this working nicely, you’ll need CSS for the <ul> and it’s <li> children. Additionally, you might want code to look out for when the up and down arrows are pressed on the keyboard and highlight the next row correctly. The code I have for this isn’t mine, so I’m not going to put it here. However, I will point out that to add any fancy jQuery on your view, i.e. to handle these up/down arrow keypress events, you can use KnockoutJS’s custom binding handlers. This is to keep business and presentation logic separate from each other. So, in JS you’d have something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* This custom binding is how knockout lets you set up your HTML elements. It's separate from the viewModel, which
* should purely deal with business logic, not display stuff.
*
* Sample usage: <input type="text" data-bind="autoComplete">
*/

ko.bindingHandlers.autoComplete = {
    /**
    * Called when the HTML element is instantiated
    */

    init: function(element, valueAccessor, allBindings, viewModel) {
        var $el = $(element);

        // specific jQuery code goes here
    },
    update: function() {} // not needed here
};

The HTML for your <ul> would change to <ul class="autocomplete-results" data-bind="autoComplete, css: {'has-results': suggestions ... – note the addition of autoComplete on the data-bind.

Read more...

How to call some Pixastic functions

21st February, 2015 - Posted by david

I was doing a bit of work with Canvas recently, manipulating images in the browser and writing the results out to files. I was looking for a package that could do various effects on the images, such as sharpen, blur etc. and came across the Pixastic package used in similar applications. However, unfortunately, the website for the package is currently down and I couldn’t find much documentation on it anywhere. So, I had to look at the source code to figure out how to call the various functions. Not the end of the world, but I just thought I’d stick some simple examples here, to maybe help get others started with the package and to direct them to the source code for more information!

I got the source code from JSdeilvr.net, which is minified, but there is unminified source on GitHub.

The 3 functions I was looking to use were sharpen, brighten/darken and blur. I’ll go through each individually. Firstly though, I’ll mention that the first parameter to each function is a Javascript Image object, which obviously has a src attribute with your image’s contents. The whole point of this was that I was building a tool with buttons which, when clicked, would perform each of the functions above on the current Image. When applying a filter, it’s best track the current level of e.g. brightness, increase/decrease this value, reset the image to how it originally looked and then apply the filter with the new value. This is better than say brightening it by 10%, then brightening the result by 10% again.

Seeing as I used brightness for the example above, I’ll start with that. Also, to darken an image, you simply reduce the brightness value, obviously.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// initial code setup
var img = new Image();
img.src = 'whatever';
img.original_src = img.src; // for doing the resets
// add the current value of each filter to the Image
$.extend(img, {brightness: 0, sharpen: 0, blur: 0});

// ...

// 'Brighten' button click handler
$('#brighten').click(function() {
    img.brightness += 2; // brighten by 2 (for darken, reduce by an amount, will work with negative values)
    // max brightness of 255
    if (img.brightness > 255) img.brightness = 255;

    // now we reset the image by creating a copy
    img.src = img.original_src;
    // now, apply the filter
    img = Pixastic.process(img, "brightness", {
        brightness : img.brightness
    });
});

For sharpen, I found it best not to sharpen by the same amount each time, it just didn’t lead to nice results.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$('#sharpen').click(function() {
    if (img.sharpen < 0.5){
        img.sharpen += 0.1;
    }
    else if (img.sharpen > 0.75){
        img.sharpen += 0.01;
    }
    else {
        img.sharpen += 0.02;
    }
    if (img.sharpen < 0.88) {
        img.sharpen = 0.88;
    }

    // now we reset the image by creating a copy
    img.src = img.original_src;
    // now, apply the filter
    img = Pixastic.process(img, "sharpen", {
        amount : img.sharpen
    });
});

Lastly, blur was a bit more straightforward, increasing by 1 every time:

1
2
3
4
5
6
7
8
9
10
$('#blur').click(function() {
    img.blur++;

    // now we reset the image by creating a copy
    img.src = img.original_src;
    // now, apply the filter
    img = Pixastic.process(imageObjs[selected_image].image, "blur", {
        amount : img.blur
    });
});

As I’m sure you’ve spotted, there’s a certain amount of repetition in the code above, starting with the line “// now we reset…”. What I did was to write a function called applyFilters, which you call once you’ve calculated your new value for brightness/sharpen/blur. This will then reset the image and apply all 3 filters. With the code above, if you were to say brighten, then blur, only the blur would be applied, as the image is reset each time. Doing it this way removes that problem.

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
function applyFilters() {
    // reset the image
    img.src = img.original_src;

    // brighten
    img = Pixastic.process(img, "brightness", {
        brightness : img.brightness
    });

    // sharpen
    img = Pixastic.process(img, "sharpen", {
        amount : img.sharpen
    });

    // blur
    img = Pixastic.process(imageObjs[selected_image].image, "blur", {
        amount : img.blur
    });
}

// ... then your handlers can look something like, e.g.
$('#blur').click(function() {
    img.blur++;
    applyFilters();
});

So, that should be enough to get you working with Pixastic. As for working with Canvas, that’s another blog post!

Read more...

Homepage takeover with existing skyscraper ads

14th February, 2015 - Posted by david

Sometimes in work we can be asked to do things we don’t like and recently I was asked to look into implementing one of those homepage takovers. Personally, I think these are awful and would like to think I wouldn’t degrade my site by implementing one, but they do make money and have a high click rate, so I can see why sites like to use them.

Normally they’re done using a fixed background wallpaper that’s clickable all the way to the edge of the page. However, I was asked to simulate this look using 2 existing skyscraper ads, 170px in width, to be positioned either side of the main content and fixed to the top of the page. Since it wasn’t entirely straightforward, I thought I’d block about it here, to help anyone else in a similar situation. I’m not going to go into the specifics of displaying the ads, simply the CSS and Javascript involved in positioning them where you want them.

I should point out, this might be possible with just CSS, but changing a site’s fundamental structure to accomodate the new columns isn’t always possible. Also, you might only want the takeover on the homepage and not other pages. This solution should have minimal impact, as it simply adds 2 divs, that can go anywhere in the HTML.

So, to describe the set-up, let’s say our main content is 1000px in width, centred in the page and we want 2 170px x 1068px divs to contain our ads and line up on the right and left of that content, as well as for the 2 ads to remain fixed at the top of the page, no matter how far we scroll down. We’ll give each of these divs a class of side-banner, with left-banner and right-banner IDs. Since these are going to be positioned explicitly, it doesn’t really matter where in the HTML you put them, maybe just inside your main content div. Initially, we’re simply going to position them in the extreme top corners of each side. I’m also going to give them different background colours, so we can know they’re positioned correctly without having to worry about loading the ads (which can come later).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.side-banner {
    width: 170px;
    height: 1068px;
    position: fixed;
    top: 0;
}
#right-banner {
    right: 0;
    background: red;
}
#left-banner {
    left: 0;
    background: green;
}

To align these alongside the content, I needed to write a Javascript function (position_banners()) to position them correctly. This function is called when the page finishes loading, as well as when the window is resized. It simply gets the body’s width, subtracts the width of our main content (remember 1000px), divides the result by 2 (as we’ve 2 sides), then further subtracts the width of our banners. This fairly basic formula works out the amount to move each div in from their corresponding edge, to line up with our main content. Then, we just use CSS left and right to position them nicely.

1
2
3
4
5
6
7
8
9
10
11
12
$(document).ready(function() {
    position_banners();
    $(window).resize(position_banners);
});

function position_banners() {
    var margin = ($('body').width() - 1000) / 2 - $('#left-banner').width(),
        left = Math.floor(margin),
        right = Math.ceil(margin);
    $('#left-banner').css({left: left + 'px'});
    $('#right-banner').css({right: left + 'px'});
}

I know this code isn’t the tidiest, but should be enough to get the idea of what you need to do.

To further enhance the ‘takeover’ effect, you could display a 970px x 250px ‘billboard’ right at the top of your main content.

Read more...

Backbone JS Models and Collections in same file with RequireJS

31st January, 2015 - Posted by david

In BackboneJS, classes/objects are called Models and groups of these models go into Collections. Using RequireJS, you define something and return that something for use in other files. In many BackboneJS examples, I’ve seen say a Person Model and a Person Collection stored in separate files, with each one being imported separately to the script they’re used in. Seeing as you generally use Models and Collections together at the same time, I was looking for a way to have them both in the one file. The solution is pretty simple and involves some simple Javascript object manipulation. I should give credit for this solution to my friend Ronan, although I’m not sure if he came up with it originally!

So, just to illustrate the normal Backbone way of doing things, sticking wih our Person class/object, you might have 2 files in your JS folder, models/person.js and collections/persons.js, 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
26
27
28
// models/person.js
define(['underscore', 'backbone'],
    function(_, Backbone) {
         Person = Backbone.Model.extend({
             url: '/person',
             // ... etc
         });

         return Person;
     }
);

//collections/persons.js
define(['underscore', 'backbone', 'models/person']
    function(_, Backbone, Person) {
        Persons = Backbone.Collection.extend({
            model: Person,
            // ... etc
        });
        return Persons;
    }
);

// some other JS file
require('models/person');
require('collections/persons');
var people = new Persons();
people.add(new Person());

So, I’m looking for a way to just have one require‘d file, that will import both my Model and Collection. A way this can be achieved is by defining a Person as a standard Javascript object, then having Model and Collection as attributes of that object. We need to add each attribute one step at a time, we can’t just do it all in one big Person = { // ... } block. Then, once the file is imported, we can do stuff like var people = new Person.Collection();.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// person.js
define(['underscore', 'backbone'],
    function(_, Backbone) {
        var Person = {}; // create initial object
        Person.Model = Backbone.Model.extend({
            url: '/person',
            // ... etc
        });

        // in order to access the Person.Model, we have to specify each attribute separately
        Person.Collection = Backbone.Collection.extend({
            model: Person.Model,
            // ... etc
        });
        return Person;
    }
);

// some other JS file
require('person');
var people = new Person.Collection();
people.add(new Person.Model());

Read more...

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...

window.onload firing too early in IE

12th May, 2012 - Posted by david

The Problem

Where I work, our tool supports 6 different languages, where translations are contained in various sets of language files. One of these sets consists of the translations stored in Javascript files as JSON blocks

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...

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...