0
votes

Trying to bind text to a global function outside the viewmodel throws the following error:

knockout.js:60 Uncaught ReferenceError: Unable to process binding "foreach: function (){return names }" Message: Unable to process binding "text: function (){return myFunction($data) }" Message: myFunction is not defined

Reproduction online

HTML

<ul data-bind="foreach: names">
    <li data-bind="text: myFunction($data)"></li>
</ul>

JS

function myFunction(text){
    return text + '--';
}

function demoViewModel() {
    self.names = ['a', 'b', 'c'];

    return self;
}

var mm = new demoViewModel();

ko.applyBindings(mm);

Instead, if I extend String object and apply the function in the following way, it works as expected:

<li data-bind="text: $data.myFunction()"></li>

Extending String object:

String.prototype.myFunction = function(){
    return this + '--';
}

Reproduction online

Why is this? Isn't there a better way to apply a global function to a text binding?

2
Not really related to your question, but note that you're missing var self = this on your demoVM; you're actually using the global window.self object as your VM right now. - Retsam
Explicitly adding it to the global context by using window.myFunction should work, right? jsfiddle.net/2prmfwyx Have a look at how knockout's bindingContext works and its internal use of the with keyword via with($data || {}) - user3297291

2 Answers

1
votes

To reference your function from your knockout templates, it needs to be attached to a ViewModel. In the simple case above, you can just attach it to demoViewModel and reference it in your template directly:

function myFunction(text){
    return text + '--';
}

function demoViewModel() {
    var self = this;
    self.names = ['a', 'b', 'c'];
    self.myFunction = myFunction;

    return self;
}

ko.applyBindings(new demoViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: names">
    <li data-bind="text: myFunction($data)"></li>
</ul>

This isn't really a "global" function, it's just a standard viewModel property and if you ended up with nested binding contexts you'd have to do $parents[n].myFunction or, if you've attached it to your root viewModel, you could do $root.myFunction.


Another way to handle this is to add the function to the binding context directly. This allows it to be referenced regardless of what the current viewModel is.

The "as" option on "foreach" binding handlers and template binding handlers is one way of adding things to the binding context; but I use a "let" bindingHandler for this purpose, the let bindingHandler isn't an official part of KO, but is often recommended by Michael Best, one of the core contributors.

function myFunction(text){
    return text + '--';
}

function demoViewModel() {
    var self = this;
    self.names = ['a', 'b', 'c'];
    self.myFunction = myFunction;

    return self;
}


//Let binding Handler
ko.bindingHandlers['let'] = {
    'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        // Make a modified binding context, with extra properties, and apply it to descendant elements
        var innerContext = bindingContext.extend(valueAccessor());
        ko.applyBindingsToDescendants(innerContext, element);

        return { controlsDescendantBindings: true };
    }
};
ko.virtualElements.allowedBindings['let'] = true;

ko.applyBindings(new demoViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<!-- ko let: {
    myFunction: myFunction
} -->
  <ul data-bind="foreach: {
      data: names,
      at: 'name'
  }">
      <li data-bind="text: myFunction($data)"></li>
  </ul>
<!-- /ko -->

In the above example, you can reference myFunction anywhere within the let binding, regardless of how many levels deep of viewModels you are.

1
votes

Here is my suggestion to do what you want . Your Function is not defined in that scope. Here you actually bind each name (even it can be an object) to your function outside the view model
Example :https://jsfiddle.net/1hz10pkc/2/
HTML :

<ul data-bind="foreach: names">
    <li data-bind="text:name "></li>
</ul> 

JS

var myFunction = function(text){
  var self = this;
  self.name = text + "--" ; 
}

function demoViewModel() {
   var self = this;
   var arr =  ['a', 'b', 'c'];
   self.names = ko.observableArray($.map(arr, function (element) {
        return new myFunction(element);
    }));
}
var mm = new demoViewModel();

ko.applyBindings(mm);