August, 2016

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

1
init

and

1
update

functions that get called when a certain value in your viewModel changes. Within your

1
init

function, you can set up various DOM-element-specific jQuery handlers, while within your

1
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

1
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

1
update

binding handler function, via the viewModel.

So, I’ll start with the viewModel. I’m going to have an observable

1
action

attribute and 2 functions

1
linkClicked

and

1
buttonClicked

. (Please bear in mind, this is a very simple example for illustration purposes, you wouldn’t really call viewModel functions

1
linkClicked

etc.!) There’ll also be a

1
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

1
action

attribute will be updated and thus trigger the

1
update

function in the

1
exampleBindingHandler

binding handler that’s applied to the

1
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

1
action

from the

1
valueAccessor

, then reset it via the viewModel’s

1
resetAction

function, just to keep things clean.

At this point we have very simple

1
alert

s 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
    1
    action

    attribute in your viewModel

  • Apply the binding handler to your main
    1
    div

    , with the observable

    1
    action

    variable as the

    1
    valueAccessor

    parameter

  • Set
    1
    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
    1
    action

Read more...