3
votes

What is the syntax (assuming there is one) to pass an input control's value to a viewmodel function? In my scenario I'm not interested in binding the input's value to a property on the viewmodel. I simply need to act on the value entered into the input control (basically I'm iterating over a collection of items and filtering out those that do not contain the entered text).

<input data-bind="text: filterText($data), valueUpdate: 'afterkeydown'">

I've tried filterText($data, value) but Knockout is trying to find value property on the viewmodel. I actually need the current value from the input control.

Is this possible?

4

4 Answers

4
votes

The typical way to handle this use case is to use a textInput binding with a subscription on the bound value that calls filterText.

<input data-bind="textInput: filter">

In your script:

filter.subscribe(function(newValue) {
    filterText(newValue)
});

which you could simplify to:

filter.subscribe(filterText);

But you're still probably thinking about this wrong. Your filtered list should be a computed that references the textInput-bound filter value to compute the filtered list.

filteredList = ko.pureComputed(function() {
    // return the list filtered by the bound filter() value
}, self);

Then you can use filteredList() wherever you need the filtered list of data.

1
votes

First things first. Your example code uses a text binding on an input node, which isn't all that useful (it would mean a one-way binding from view model to DOM, the "text" content of that input which is not meaningful). Given the valueUpdate binding present you probably meant the value binding? Note by the way that the textInput binding is the way to combine those two in modern versions of KnockoutJS.

As to your actual question, important context is missing: why do you (think you) want to do this? Depending on the context, the solution will be different, or you might even turn out to have an XY-problem.

In any case, to the answers.

Option 1
Trying to answer the specific question nonetheless, I second @JohnnyHK's answer of writing a .subscribe on your observable as one possible solution.

Option 2
Another possible solution closely resembling that one, but trying to satisfy "I'm not interested in the input's value" part, I suggest using a read only computed observable:

function Root() {
  var self = this;
  
  self.filter = ko.computed({
    read: function() { return ""; }, // <-- not recommended, read context!
    write: function(newValue) {
      // You're free to discard the newValue   <-- not recommended, read context!
      alert("Input has been changed.");
    }
  });
}

ko.applyBindings(new Root());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<input data-bind="textInput: filter">

Now this explicitly discards the newValue in the write bit (as per your request), and thus also has a weird-looking read function. I don't recommend this, and instead would recommend using a private variable with a backing observable for reading and writing. Effectively though, that would make this option equivalent to the first option from the other answer.

An interesting note though, your question's bit about "the collectin" and filtering input: if you look at the docs from writeable computeds you'll find that accepting users input or not if it satisfies a certain condition is in fact a use case for this.

Option 3
What I'd recommend though, if I rephrase your request a bit:

How do you react to input value changes with idiomatic KnockoutJS when your action won't care about the actual value?

For writing custom interaction with the DOM, there's custom binding handlers. You could either have one that does not use the ViewModel at all, or one that has an observable.

Option 3.A

ko.bindingHandlers["opacitor"] = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    $(element).on("keydown", function() {
      $(this).animate({ opacity: 0.05 }, 1000, function() { $(this).animate({ opacity: 1.0 }, 1000); });
    });
  }
}

ko.applyBindings({});
input { background-color: gold; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>

<input data-bind="opacitor">

Option 3.B
Or a second option that does use the view model:

ko.bindingHandlers["opacitor"] = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    $(element).on("keydown", function() {
      var speed = ko.utils.unwrapObservable(valueAccessor)();
      $(this).animate({ opacity: 0.05 }, speed, function() { $(this).animate({ opacity: 1.0 }, speed); });
    });
  }
}

// speed1 and speed2 could also be observables
ko.applyBindings({ speed1: 500, speed2: 2000 });
input { background-color: gold; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>

<input data-bind="opacitor: speed1">
<input data-bind="opacitor: speed2">

The downside of both option 3A and 3B is that you need to write custom event handling logic. If you have a peek at the textInput source from KO you'll see that it's not trivial to handle these things correctly cross-browser. So using Option 1 or 2 might still be better (even though you'll have some view model interaction even though "you're not interested").

Option 4
Use the event binding. This suffers from the same drawbacks as option 3, but might be a straightforward solution nonetheless. Here's an example:

function Root(){
  var self = this;
  self.myFn = function(data, element) {
    console.log(element.target.value);
    return true;
  };
}

ko.applyBindings(new Root());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<input data-bind="event: { keyup: myFn }">

One subtle change you'll see in that last one, and a question I want to end with: did you in fact not have this issue, where you actually need the new value and thus should not be looking at afterkeydown but for a different event?

1
votes

Use $element.value to access the value of the input.

0
votes

If you are not binding the property to the view-model then you can use jquery/javascript to get the value of that input control directly inside your view-model function by doing something like this.

self.Filter= function () {           
   var filter = $('#textBoxID').val();
   //    
}

This way you do not need to pass the value to your view-model function parameter.