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

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

jQuery Mobile and Dynamic Data

23rd May, 2011 - Posted by david

UPDATE: I’ve recently been asked by entertainment.ie to stop scraping thier licensed data, to which I’ve duly agreed. Thus, the app is no longer live. However, the lessons learned below are still valid. END

Haven’t posted for a while, mainly because I’ve been busy trying to teach myself Ruby on Rails, so haven’t created anything new in my spare time. Have come up with a few interesting fixes/ways to do things in relation to Facebook code in work though, so will hopefully do a post on those in the near future.
In the meantime, I wrote a very small little web application last week using the pretty cool jQuery Mobile framework. The app, called What’s on now (link removed, see UPDATE above), is simply a list of what’s on now and next on Ireland’s 17 basic cable channels. The data is pulled from a similar page on entertainment.ie, with the unnecessary channels filtered out. At the moment the app is pretty simple, but I plan to add to it over time (e.g. to add an option for the non-basic channels), updating this article as I go.

I did this mainly to have a quick go with jQuery Mobile, to see what it could do. I could’ve used PHP and built a mobile-sized HTML page, but it’s always good to try new things! Most of the development was pretty straight forward; however, because the data is retrieved dynamically every time the page is loaded, there’s a couple of tricks you need to apply to get the jQuery Mobile framework to do it’s magic.

The main page you’re greeted with is simply a glorified un-ordered list of programmes, with separator list items to distinguish the channels. I’m not going to go into the details of how you need to structure a page (see links at the end of this post) but here’s a snippet of the HTML:

1
2
3
4
5
6
7
<ul id="channels" data-role="listview">
    <li data-role="list-divider">RTE One</li>
    <li><a href="#RTE-One-1">21:00: 9 O'Clock News</a></li>
    <li><a href="#RTE-One-2">21:35: Prime Time</a></li>
    <li data-role="list-divider">RTE Two</li>
.. etc.
</ul>

When the page loads, ul#channels is empty. The data is called via a jQuery GET, which gets over cross-domain restrictions by using YUI, thanks to the Cross-Domain AJAX mod. The relevant data is filtered out and formatted and each of the li‘s are built and inserted into #channels. At this point, if you look at the page in your browser, it’ll still look like an ordinary list – you need to tell jQuery to work it’s magic on the dynamically created data. In this instance it’s done as follows:

1
$("#channels").listview("refresh");

Once I had my list of programmes, I thought I may as well add the info for each program, seeing as I already had the data at my disposal. The route I decided to go down here was to create new ‘page’ div‘s for each program, each one having it’s own ID, then link to each of these pages from the li‘s. Again, the scope of building one of these pages is beyond this blog post and well documented elsewhere, but here’s a quick sample:

1
2
3
4
5
6
7
8
9
10
11
12
<div data-role="page" id="RTE-One-1" data-url="RTE-One-1">
    <div data-role="header">
        <h1>RTE one</h1>
    </div>
    <div data-role="content">
        <h2>9 O'Clock News: 21:00</h2>
        An update on the latest domestic and international events of the day.
    </div>
    <div data-role="footer">
        © David Coen 2011</div>
    </div>
</div>

This is simply added to the body using $('body').append(page); (where the variable page is a string of HTML such as the above). So, again here you need to tell jQuery Mobile that you’ve added a new page, so it can do it’s magic. This is achieved by the one simple line:

1
$('#RTE-One-1').page();

Hopefully this post will clear up a couple of things for anyone using jQuery Mobile with dynamically generated data. As I promised, here are some links of articles that helped me get a better understanding of the framework:

Full implementation code (UPDATE 2)

I was requested by @danny to post the full source code, seeing as the app is actually no longer available, so I’ve decided to put most of it here. I excluded some of the scrape JS (as indicated in the code comments) to prevent the app being re-used somewhere else.

So, first up, is the initial HTML page, with header and footer blocks, an empty content block and the JS and CSS includes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<title>What's on now</title>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a1/jquery.mobile-1.0a1.min.css">
<script type="text/javascript" src="./jquery.min.js"></script>
<script type="text/javascript" src="./jquery-mobile.min.js"></script>
<script type="text/javascript" src="./whatson.js"></script>
<div data-role="page" id="home">
<div data-role="header">
    <h1>What's on now</h1>
</div>
<div data-role="content">
    <ul id="channels" data-role="listview"></ul>
</div>
<div data-role="footer" style="text-align: center;">
    <a href="http://www.drcoen.com">© David Coen 2011</a></div>
</div>

Next up is the javascript file, whatson.js in the above. Don’t forget, the $.ajax call has been overwritten by the Cross-Domain AJAX plug-in I mentioned earlier. Addtionally, I’ve used a few functions from php.js. to replicate this functionality in JS, namely str_replace, in_array and trim. I’ve excluded them here but they can be found in the php.js website.

Also, just to re-clarify, the page i was scraping had a list of channels and programs that were on now (prog1) and next (prog2).

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
$(document).ready(function() {
    var cable_channels = [
        'RTE One',
        'RTE Two',
        'BBC One',
        // etc......
    ];
    $.mobile.pageLoading();
    $.ajax({
        url: // URL for the data
        type: 'GET',
        success: function(data) {
            html = $(data['responseText']);
            // for each channel scrapped
            $('td.listing-channel', html).each(function(){
            var channel = // code omitted
            var prog1_start, prog2_start, prog1, prog2, prog1_name, prog2_name;
            // if it's a channel I'm interested in
            if (in_array(channel, cable_channels))
            {
                // get the name, start time and description of the program currently being shown on the channel
                prog1_start = // start time of 1st program - code omitted
                prog1_name = // name of 1st program - code omitted
                prog1 = // description of 1st program - code omitted

                // do the same for the one on after it
                prog2_start = // start time of 2nd program - code omitted
                prog2_name = // name of 2nd program - code omitted
                prog2 = // description of 2nd program - code omitted

                // replace spaces with '-' for a valid #id
                var id = str_replace(' ', '-', channel);

                //create new block on the main page for our channel and it's 2 programs
                var li = '<li data-role="list-divider">' + channel + '</li>' +
                    '<li><a href="#' + id + '-1">' + prog1_start + ': ' + prog1_name + '</a></li>' +
                    '<li><a href="#' + id + '-2">' + prog2_start + ': ' + prog2_name + '</a></li>';
                $('#channels').append(li);

                // create a new page for the program description - clicking on the program in the <li> above will
                // bring you to this new description page
                var page = '<div data-role="page" id="'+id+'-1" data-url="'+id+'-1">' +
                    '<div data-role="header">' +
                        '<h1>' + channel + '</h1>' +
                    '</div>' +
                    '<div data-role="content">' +
                        '<h2>' + prog1_name + ': ' + prog1_start + '</h2>' + prog1 +
                    '</div>' +
                    '<div data-role="footer" style="text-align: center;">' +
                        '<a href="http://www.drcoen.com">© David Coen 2011</a>' +
                    '</div></div>';
                    $('body').append(page);
                    $('#'+id+'-1').page();

                    // Do same again for 2nd program
                    page = '<div data-role="page" id="'+id+'-2" data-url="'+id+'-2">' +
                        '<div data-role="header">' +
                            '<h1>' + channel + '</h1>' +
                        '</div>' +
                        '<div data-role="content">' +
                            '<h2>' + prog2_name + ': ' + prog2_start + '</h2>' + prog2 +
                        '</div>' +
                        '<div data-role="footer" style="text-align: center;">' +
                            '<a href="http://www.drcoen.com">© David Coen 2011</a>' +
                        '</div></div>';
                    $('body').append(page);
                    $('#'+id+'-2').page();
                }
            });
            $("#channels").listview("refresh");
            $.mobile.pageLoading(true); // kill the page loading modal
        }
    });
});

I realise this code could be alot cleaner, but the app was still in it’s early stages before I was asked to take it down, thus I haven’t spent time tidying it up. Hopefully there’s enough here to figure out how to do what you need!

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