Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filter Table Contents

Tags:

knockout.js

I am looking to implement a binding to search an HTML table.

I thought about implementing a custom binding and utilizing jQuery for the heavy lifting. The custom binding aspect is to make it universally accessible within my solution. My current solution consists solely of jQuery and doesn't use any KnockoutJS functionality.

The problem: How can I pass in a "target" table for which to search? ko.customBindings only have access to the element which it is bound to, and I need to use an input, so I didn't know if there was a way to override a parameter and pass in the target id of the table, or something. Getting the search term would be easy via the valueAccessor, but I didn't know if I could use

Again, I want to make this functionality "lift & move," so if there's a better general way to go about this I'm certainly not aware of it nor have I discovered it searching for a solution. I'm under the assumption that a customBinding is a good solution because of how I've seen it being used, but would not argue against other solutions.

I was thinking something like :

ko.bindingHandlers.searchTable = {
    update: function(element, valueAccessor, tableIdToSearch) {
        var term = ko.unwrap(valueAccessor());        
        //check if table exists
        // $.each(tableIdToSearch).find("tr"), function() .. use term
    }  
};
like image 586
Mark C. Avatar asked Feb 10 '23 16:02

Mark C.


1 Answers

With Knockout and the MVVM pattern in general it's often wise to keep the View "dumb", merely reflecting (or modifying, with two-way bindings) the current state of the ViewModel.

I'm not 100% sure what you mean by "search an HTML table", but it would make more sense to me to search the tabular data itself. For small and medium size cases a computed observable array could do the work for you, like this:

var ViewModel = function() {
  var self = this;
  
  self.filter = ko.observable('');
  
  self.items = ko.observableArray(["apples", "apple pie", "apple sauce", "pumpkin pie", "peaches"]);
  
  self.filteredItems = ko.computed(function() {
    var filter = self.filter();
    if (!filter) { return self.items(); }
    return self.items().filter(function(i) { return i.indexOf(filter) > -1; });
  });
};

ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Search: <input data-bind="textInput: filter" />
<table>
  <tbody data-bind="foreach: filteredItems">
    <tr><td data-bind="text: $data"></td></tr>
  </tbody>
</table>

Obviously there are a few details to iron out (case sensitivity, throttling, etc), but the general idea is there. There's many different ways to up the performance, but first check if you even need to do that; don't optimize too early! You haven't specified any actual criteria in the question for performance, so just trying this approach is your best bet.

This is also probably the "Knockout/MVVM" style solution. I find that you typically get cleanest code if you only minimally use jQuery if you're also using Knockout...


If you really do want to actually search a "HTML table", i.e. filter DOM not generated by Knockout, but still use a custom binding, that is possible. Here's one example way to go about that:

ko.bindingHandlers["filterTable"] = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {

  },
  update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    var filter = ko.utils.unwrapObservable(valueAccessor());
    
    $(element).find('tr').each(function(index, row) {
      if ($(row).find('td:contains("'+filter+'")').length > 0) {
        $(row).show();
      }
      else {
        $(row).hide();
      }
    });
  }
};

$(function() {
  var ViewModel = function() {
    var self = this;
    
    self.filter = ko.observable('');
  };
  
  var items = ["apples", "apple pie", "apple sauce", "pumpkin pie", "peaches"];
  
  ko.applyBindings(new ViewModel());  
});
<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.2.0/knockout-min.js"></script>

Search: <input data-bind="textInput: filter" />
<table data-bind="filterTable: filter">
  <tbody>
    <tr><td>apples</td></tr>
    <tr><td>apple pie</td></tr>
    <tr><td>apple sauce</td></tr>
    <tr><td>pumpkin pie</td></tr>
    <tr><td>peaches</td></tr>
  </tbody>
</table>

Again, lots of details to work out (case sensitivity and other filtering details), but I can't make a more specific suggestion because you haven't provided any specifics in your question. You'd have to adapt the above solution to your own situation.

My example uses show and hide, but a "lift & move" solution where you park said tr elements in a different hidden table would work equally fine here, because Knockout is not in control of the table DOM.

like image 119
Jeroen Avatar answered Feb 21 '23 02:02

Jeroen