Ana Isabel
Ana Isabel

Reputation: 971

How do I show the results of a filtered list using Knockout.Js?

I am building an app that takes a list of restaurants and displays them on a Google Map. I also display this list so the user can see the names of the restaurants.

I want to filter the list using Knockout.js so that when someone starts typing the name of the restaurant, only the one's that match the query appear.

HTML + Knockout Bindings

<div>
  <div id="search">
    <input type="text" placeholder="Filter" data-bind="textInput:query">
  </div>
  <ul data-bind="foreach: restaurants">
    <li data-bind="text: name"></li>
  </ul>
</div>
<div id="map"></div>

JavaScript

var restaurants = [
    {name: 'Chez Simone', location: {lat:48.8603937, lng: 2.3430545}},
    {name: 'Cafe Coutume', location: {lat:48.851599, lng: 2.3162123}},
    {name: 'Café Pinson', location: {lat:48.863732, lng: 2.3631037}},
    {name: 'Sol Semilla', location: {lat:48.8730959, lng: 2.363135900000001}},
    {name: 'Juice Lab Marais', location: {lat:48.8563595, lng: 2.3637123}}
];

//viewModel
var viewModel = function(){
  var self = this;
  self.restaurantList = ko.observableArray(restaurants);

  //Stores user input
  self.query = ko.observable('');

  // Filters through observableArray and filters results using util.arrayFilter();
  self.search = ko.computed(function(){
    var query = this.query().toLowerCase();
    if(!query) {
      return self.restaurantList();
    } else {
      //go through restaurant list and for each restaurant, check if the letters in query
      // appear in restaurant name. If so, display name. else don't.
      var restaurantList = self.restaurantList();
      for(i = 0; i < restaurantList.length; i++){
        var restaurant = restaurantList[i];
        var restaurantName = restaurant.name;
        // check if query letter/s appears in restaurant name
        if(restaurantName.toLowerCase().indexOf(query) > -1){
        // Help! This is where I'm stuck. 
         return restaurantName;
        }
      }
    }
  }, self);
}; // viewModel ends `

The logic I have so far works - if I use console.log(restaurantName) it prints out only the correct restaurants. I can't get it to appear in the view.

How do I display it in the list view?

Upvotes: 3

Views: 543

Answers (1)

user3297291
user3297291

Reputation: 23372

Your computed property search should always return an array so you can bind the foreach to the filtered collection.

You already got the first part:

if (!query) return self.restaurantList();

In other words, if you're not searching, show all restaurants.

You also succeeded to get most of the last part working, except the part where you loop and return.

You'll have to use restaurantList.filter (or, as you mention in your comment, util.arrayFilter) to create an array of items that match your query.

 return self.restaurantList().filter(function(restaurant) {
     return (restaurant.name.toLowerCase().indexOf(query) > -1)
 });

Then, with foreach: search, you'll get the snippet below:

var restaurants = [
    {name: 'Chez Simone', location: {lat:48.8603937, lng: 2.3430545}},
    {name: 'Cafe Coutume', location: {lat:48.851599, lng: 2.3162123}},
    {name: 'Café Pinson', location: {lat:48.863732, lng: 2.3631037}},
    {name: 'Sol Semilla', location: {lat:48.8730959, lng: 2.363135900000001}},
    {name: 'Juice Lab Marais', location: {lat:48.8563595, lng: 2.3637123}}
];

//viewModel
var viewModel = function(){
  var self = this;
  self.restaurantList = ko.observableArray(restaurants);

  //Stores user input
  self.query = ko.observable('');

  self.search = ko.computed(function(){
    var query = this.query().toLowerCase();
    if(!query) {
      return self.restaurantList();
    } else {
      var restaurantList = self.restaurantList();
      return restaurantList.filter(function(restaurant) {
        var restaurantName = restaurant.name;
        return (restaurantName.toLowerCase().indexOf(query) > -1)
      });
    }
  }, self);
};

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

<div>
  <div id="search">
    <input type="text" placeholder="Filter" data-bind="textInput:query">
  </div>
  <ul data-bind="foreach: search">
    <li data-bind="text: name"></li>
  </ul>
</div>
<div id="map"></div>

Note that to update any markers in the google maps UI, you'll have to subscribe to search and update your map accordingly.

Upvotes: 4

Related Questions