Mingo
Mingo

Reputation: 936

Add filter row to dynamically created knockout.js grid

I have view model which i am using to bind to a table of results. The view dynamically renders the table of results without any prior knowledge of number or name of columns, which is great. Similar to that described here and in the interest of keeping generic and not domain specific i have adapted the code sample from that questions answer here.

However i need to add a filter row to allow the user to filter, i have added a row of inputs based on the column names in the code below and attempted to bind them to the view model. It is important that they are bound to the view model so that when i refresh the grid the view model knows about any filters applied.

I have tried a couple of things

Any thoughts would be greatly appreciated

Many thanks, Ed

JS

var VM = function () {
  var self = this;
  self.items = ko.observableArray();
  self.filters = ko.observable({});

  self.columnNames = ko.computed(function () {
    if (self.items().length === 0)
        return [];
    var props = [];
    var obj = self.items()[0];
    for (var name in obj)
        props.push(name);
    return props;


  });

};
var vm = new VM();

ko.applyBindings(vm);

vm.items.push({
  'Name': 'John',
  'Age': 25
});
vm.items.push({
'Name': 'Morgan',
'Age': 26
});

View :

<table>
  <thead>
    <tr data-bind="foreach: columnNames">
        <th> <span data-bind="text: $data"></span>

        </th>
    </tr>
  </thead>
  <tbody >

   <!-- add filter rows -->
    <tr data-bind="foreach: $root.columnNames">
        <td ><input type='text' data-bind="value: $root.filters[$data]"/></td>
    </tr>
   <!-- add the items -->
   <!-- ko foreach: items -->
   <tr data-bind="foreach: $parent.columnNames">
        <td data-bind="text: $parent[$data]"></td>
    </tr>
   <!-- /ko -->
  </tbody>
</table>

Upvotes: 1

Views: 2868

Answers (1)

Damien
Damien

Reputation: 8987

I made a fiddle in which filters are stored into an object {Col1 : Value1, Col2 : Value2...}.

Now the columnNames computed returns an object that contains both the header of the column and its filter.

I also created a filteredItems computed that contains only items which matched the filters.

The subscriptions array aims to keep a track of subscriptions in order to delete them when the number of column changes.

 var VM = function () {
    var self = this;
    self.items = ko.observableArray();
    self.filters = ko.observable({});
    self.filteredItems = ko.computed(function () {
        var filters = self.filters();

        var items = ko.utils.arrayFilter(self.items(), function (item) {
            for (var col in filters) {
                var v = (item[col] || '').toString(); // cell value
                var f = filters[col]; // what you typed in header
                if (v.indexOf(f) < 0) return false; // filter is contains
            }
            return true;
        });
        return items;
    });
    var subscriptions = [];
    self.columnNames = ko.computed(function () {
        // clean old subscriptions
        ko.utils.arrayForEach(subscriptions, function (s) { s.dispose(); });
        subscriptions = [];
        if (self.items().length === 0) return [];
        var props = [];
        var obj = self.items()[0];
        for (var name in obj) {         
            var p = { name: name, filter: ko.observable('') };
            // subscribe : so when you type something, filterOnChanged will be invoked.
            subscriptions.push(p.filter.subscribe(filterOnChanged, p));
            props.push(p);
        }
        return props;
    });

    var filterOnChanged = function (value) {
        console.log([this.name, value]);
        var filters = self.filters();
        filters[this.name] = value;
        self.filters(filters);
    };

};

See fiddle

Upvotes: 5

Related Questions