0
votes

My question is regarding the click binding in Knockout, and why the following behaviour occurs. I have found many click binding questions on here, but not specific to the behaviour I describe below.

I understand that in Knockout click binding with parameters are executed on page load as Knockout expects a reference (to a function) so when it comes across a function call within a binding it will execute that function expecting a returned function reference to bind to. So if I returned another reference to a function, it would be that function which is executed on the elements click.

So far so good, that makes sense.

In order to see this for myself, I quickly created this:

HTML:

<input data-bind="click: selectImportType(1)" type="button" />

JS

var functiontest = function () {
    alert('test');
};

viewModel.selectImportType = function (type) {
    viewModel.selectedImport(type);

    return functiontest;
}

Upon executing this, I have found that the 'functiontest' reference is returned to the binding as the click calls the functiontest function as expected.

Now to my confusion. I found that the selectImportType function is also called when the element is clicked. selectImportType is called first followed by a call to the functiontest function.

How is this possible? The reference resolved during the binding was to the functiontest function!

1

1 Answers

2
votes

When your write

data-bind="click: selectImportType(1)"

the expression selectImportType(1) is evaluated and it returns your functiontest function and then it is assigned to click binding.

Why?

Because it's by design.

When knockout parses binding it creates bindings string evaluator that is just dynamic function:

function createBindingsStringEvaluator(bindingsString, options) {
  // Build the source for a function that evaluates "expression"
  // For each scope variable, add an extra level of "with" nesting
  // Example result: with(sc1) { with(sc0) { return (expression) } }
  var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString, options),
      functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
  return new Function("$context", "$element", functionBody);
}

In your case functionBody is:

"with($context){with($data||{}){return{'click':function(){return selectImportType(1) }}}}"

And it is evaluated to get parsedBindings (https://github.com/knockout/knockout/blob/v3.4.1/src/binding/bindingProvider.js#L27) like this

{
  "click": function(){return selectImportType(1)}
}

that will be passed to bindginHandler the following way:

var valueAccessor = getValueAccessor(bindingKey);
                                        ||
                                       click

So valueAccessor is function:

function(){return selectImportType(1)}

Then let's look at source code event binding( https://github.com/knockout/knockout/blob/v3.4.1/src/binding/defaultBindings/event.js#L6-L18):

ko.bindingHandlers[eventName] = {
  'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
     var newValueAccessor = function () {
       var result = {};
       result[eventName] = valueAccessor(); // 1
       return result;
     };
     return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindings, viewModel, bindingContext);
   }
}

ko.bindingHandlers['event'] = {
  'init' : function (element, valueAccessor, allBindings, viewModel, bindingContext) {
      var eventsToHandle = valueAccessor() || {}; // 2

As you can see the valueAccessor is wrapped in newValueAccessor and when knockout is getting handler for click event (see comment 2) newValueAccessor is also evaluated and you get result of execution selectImportType(1)

Finally eventsToHandle will be your functiontest:

function () {
    alert('test');
}

That's why the functiontest function is called when clicking the button


I guess you're looking for something like:

data-bind="click: selectImportType.bind($data, 1)"

In this case selectImportType function will be called only after clicking on the button element.

There are a lot of question about how to pass parameters to a function within knockout binding