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.

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:

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

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

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:

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

Tags: javascript jquery knockoutjs | david | 5th Aug, 2016 at 18:28pm | No Comments

No Comments

Leave a reply

You must be logged in to post a comment.