Endorel
Endorel

Reputation: 31

How to use user input with filter function in Knockout.js

I have a filter function that I would like to adapt to user input, but I don't really know how to do it. I'm fairly new to Knockout so I would appreciate some help with this.

When I click the filter button (see code below) i get this in the input area:

function observable() {
    if (arguments.length > 0) {
        // Write
        // Ignore writes if the value hasn't changed
        if (observable.isDifferent(observable[observableLatestValue], arguments[0])) {
            observable.valueWillMutate();
            observable[observableLatestValue] = arguments[0];
            observable.valueHasMutated();
            return this; // Permits chained assignments
        } else {
            // Read
            ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
            return observable[observableLatestValue];
        }
}

What I want to achieve is to write a value in the input area, click the button (for now, will use submit later) and have the search results filtered. The array employeeList is an observable array that is populated through an ajax call (the search function).

KO Code:

self.employeeList = ko.observableArray([]);
self.currentFilter = ko.observable();

self.filterEmpl = ko.computed(function () {
            if (!self.currentFilter()) {
                return self.employeeList();
            } else {
                return ko.utils.arrayFilter(self.employeeList(), function (employee) {
                    return employee.DepartmentName == self.currentFilter();
                });
            }
        });

        self.filter = function (value) {
            self.currentFilter(value);

    } //filter

HTML:

<form>
    <input type="text" placeholder="Department" id="department" class="filterInput" data-bind="value: currentFilter" />
    <button data-bind="click: function () { filter(currentFilter) }">Filter</button>
    <br />
    <input type="text" placeholder="Office" class="filterInput" />
    <br />
    <input type="text" placeholder="Skills" class="filterInput lastInput" />
</form>

Thanks!

Upvotes: 1

Views: 603

Answers (1)

user3297291
user3297291

Reputation: 23372

Your filterEmpl is a ko.computed. This means it automatically updates once one of the observable values it uses is updated.

In your case, it will update whenever either self.employeeList or self.currentFilter changes.

To try this out, type one of the DepartmentNames in the example below. Once you remove focus from the input, the value data-bind updates currentFilter, and self.filterEmpl is updated.

var VM = function() {
  self.employeeList = ko.observableArray([
    { DepartmentName: "Test1", Name: "Employee 1" },
    { DepartmentName: "Test2", Name: "Employee 2" }
  ]);
  self.currentFilter = ko.observable();

  self.filterEmpl = ko.computed(function() {
    if (!self.currentFilter()) {
      return self.employeeList();
    } else {
      return ko.utils.arrayFilter(self.employeeList(), function(employee) {
        return employee.DepartmentName == self.currentFilter();
      });
    }
  });


}

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

<h2>Type "Test1" or "Test2" and blur focus to filter</h2>
<form>
  <input type="text" placeholder="Department" data-bind="value: currentFilter" />
</form>

<h2>All employees:</h2>
<ul data-bind="foreach: employeeList">
  <li data-bind="text: Name"></li>
</ul>

<h2>Filtered employees:</h2>
<ul data-bind="foreach: filterEmpl">
  <li data-bind="text: Name"></li>
</ul>

Now, if you want to filter only when a button is pressed, you don't need the ko.computed. You define a second ko.observableArray and write to it from within the filter function. Note that you don't need to pass it any arguments; the viewmodel is already aware of the currentFilter value via the value binding.

var VM = function() {
  self.employeeList = ko.observableArray([
    { DepartmentName: "Test1", Name: "Employee 1" },
    { DepartmentName: "Test2", Name: "Employee 2" }
  ]);
  self.currentFilter = ko.observable();

  self.filterEmpl = ko.observableArray(self.employeeList());
  
  self.filter = function() {
    var result = self.employeeList(),
        filter = self.currentFilter();
    
    if (filter) {
      result = ko.utils.arrayFilter(result, function(employee) {
        return employee.DepartmentName == filter;
      });
    }
    
    self.filterEmpl(result);
  };


}

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

<h2>Type "Test1" or "Test2" and tap button to filter</h2>
<form>
  <input type="text" placeholder="Department" data-bind="value: currentFilter" />
  <button data-bind="click: filter">filter</button>
</form>

<h2>All employees:</h2>
<ul data-bind="foreach: employeeList">
  <li data-bind="text: Name"></li>
</ul>

<h2>Filtered employees:</h2>
<ul data-bind="foreach: filterEmpl">
  <li data-bind="text: Name"></li>
</ul>

Personally, I like to use the computed approach. You can extend the observable using the rateLimit option if performance is limiting. Ultimately, it's mostly a UX decision.


P.S. The input you did get in the input area is knockout's definition of the ko.observable function. In your <button>, you pass currentFilter without getting its value using currentFilter(). In filter, you write this to currentFilter which is data-bound to the <input>. I figured it'd be more useful to explain the two approaches, but you still might want to know where the strange input came from...

Upvotes: 1

Related Questions