UPDATE:
I had to improve the code that I wrote more than a year ago this week, and with more tests, this is what I came across. This method is much faster than the old one, and I mean a lot! Tested with a node depth of 5 nodes and 5100 lines . This data preparation takes about 1.3s , but if you do not need a case-insensitive search, deleting toLowerCase will be half that time up to 600 ms . When the search strings are prepared, the search is performed instantly.
This is from our setData function, where we prepare the data
var self = this, searchProperty = "name"; //if it a tree grid, we need to manipulate the data for us to search it if (self.options.treeGrid) { //createing index prop for faster get createParentIndex(items); for (var i = 0; i < items.length; i++) { items[i]._searchArr = [items[i][searchProperty]]; var item = items[i]; if (item.parent != null) { var parent = items[item.parentIdx]; while (parent) { parent._searchArr.push.apply( parent._searchArr, uniq_fast(item._searchArr) ); item = parent; parent = items[item.parentIdx]; } } } //constructing strings to search //for case insensitive (.toLowerCase()) this loop is twice as slow (1152ms instead of 560ms for 5100rows) .toLowerCase(); for (var i = 0; i < items.length; i++) { items[i]._search = items[i]._searchArr.join("/").toLowerCase(); items[i]._searchArr = null; } //now all we need to do in our filter is to check indexOf _search property }
In the above code, I use some functions. The first creates two properties: one for its position in the array and the second parentIdx for the parent index. I'm not sure if this speeds up performance, but eliminates the need for a nested loop in the setData strong> functions .
The one that actually matters here is uniq_fast , which takes an array and removes all duplicates. This method is one of many functions from this answer remove-duplicates-from-javascript-array
function createParentIndex(items) { for (var i = 0; i < items.length; i++) { items[i].idx = i; //own index if (items[i].parent != null) { for (var j = 0; j < items.length; j++) { if (items[i].parent === items[j].id) { items[i].parentIdx = j; //parents index break; } } } } } function uniq_fast(a) { var seen = {}; var out = []; var len = a.length; var j = 0; for (var i = 0; i < len; i++) { var item = a[i]; if (seen[item] !== 1) { seen[item] = 1; out[j++] = item; } } return out; }
Now, with all this data preparation, our filtering function is actually becoming quite small and easy to use. The filter function is called for each element, and since we now have the _search property for each element, we just check this. If the filter is not applied, we need to make sure that we do not show closed nodes
function treeFilter(item, args) { var columnFilters = args.columnFilters; var propCount = 0; for (var columnId in columnFilters) { if (columnId !== undefined && columnFilters[columnId] !== "") { propCount++; if (item._search === undefined || item._search.indexOf(columnFilters[columnId]) === -1) { return false; } else { item._collapsed = false; } } } if (propCount === 0) { if (item.parent != null) { var dataView = args.grid.getData(); var parent = dataView.getItemById(item.parent); while (parent) { if (parent._collapsed) { return false; } parent = dataView.getItemById(parent.parent); } } } return true; }
So, the question was asked a long time ago, but if someone is looking for an answer for this, use the code above. It's fast, but any code improvements would be very convenient!
END OF EDITING
old answer (this is very slow):
As a start, you need to create a filter function that you use with your dataView. DataView will call your function as soon as you type something. The function will be called for each row in the dataView, passing the row as the item parameter. Returning false indicates that the line should be hidden, and true for visible.
Looking at the Example Tree , the filter function looks like
function myFilter(item, args) { if (item["percentComplete"] < percentCompleteThreshold) { return false; } if (searchString != "" && item["title"].indexOf(searchString) == -1) { return false; } if (item.parent != null) { var parent = data[item.parent]; while (parent) { if (parent._collapsed || (parent["percentComplete"] < percentCompleteThreshold) || (searchString != "" && parent["title"].indexOf(searchString) == -1)) { return false; } parent = data[parent.parent]; } } return true; }
In my first attempt to do this, I tried to manipulate the parent so that he would not hide. The problem is that I do not know how to display it, and the problem is that you do not know in which order the rows will be filtered (if the parent row is the last to be filtered, the parent property is null)
I abandoned this thought and tried to work with the element passed to the method, as it was provided. The way to do this when working with the main parent / child tree structures is to use recursion .
My decision
To get started, create a function that contains all the filtering and returns true or false. I used a fixed title bar for quick filters as a base, and then added my own rules to it. This is a really stripped-down version of my realFilter function, so you may need to tweak it a bit.
function realFilter(item, args) { var columnFilters = args.columnFilters; var grid = args.grid; var returnValue = false; for (var columnId in columnFilters) { if (columnId !== undefined && columnFilters[columnId] !== "") { returnValue = true; var c = grid.getColumns()[grid.getColumnIndex(columnId)]; if (item[c.field].toString().toLowerCase().indexOf( columnFilters[columnId].toString().toLowerCase()) == -1) {
Secondly, this is the time for a recursive function. This is the hard part if you are not familiar with how they work.
//returns true if a child was found that passed the realFilter function checkParentForChildren(parent, allItems, args) { var foundChild = false; for (var i = 0; i < allItems.length; i++) { if (allItems[i].parent == parent.id) { if (realFilter(allItems[i], args) == false && foundChild == false) //if the child do not pass realFilter && no child have been found yet for this row foundChild = checkParentForChildren(allItems[i], allItems, args); else return true; } } return foundChild; }
Finally, we implement the function of the original filter. This is a function that is called by slickgrid and must be registered in the dataView
//registration of the filter dataView.setFilter(filter); //the base filter function function filter(item, args) { var allRows = args.grid.getData().getItems(); var columnFilters = args.columnFilters; var grid = args.grid; var checkForChildren = false; for (var i = 0; i < allRows.length; i++) { if (allRows[i].parent == item.id) { checkForChildren = true; break; } } for (var columnId in columnFilters) { if (columnId !== undefined && columnFilters[columnId] !== "") { var c = grid.getColumns()[grid.getColumnIndex(columnId)]; var searchString = columnFilters[columnId].toLowerCase().trim(); if (c != undefined) { if (item[c.field] == null || item[c.field] == undefined) { return false; } else { var returnValue = true; if (checkForChildren) { returnValue = checkParentForChildren(item, allRows, args); if(!returnValue) returnValue = realFilter(item, args); } else returnValue = realFilter(item, args); if (item.parent != null && returnValue == true) { var dataViewData = args.grid.getData().getItems(); var parent = dataViewData[item.parent]; while (parent) { if (parent._collapsed) { parent._collapsed = false; } parent = dataViewData[parent.parent]; } } return returnValue; } } } } if (item.parent != null) { var dataViewData = args.grid.getData().getItems(); var parent = dataViewData[item.parent]; while (parent) { if (parent._collapsed) { return false; } parent = dataViewData[parent.parent]; } } return true; }
I am currently working on this, so I have not bothered to improve the code yet. It works as far as I know, but you may need to configure some things in the filter and realFilter so that it works as you expect. I wrote this today so that it is not tested more than at the development stage.
Note: If you want to use a different input for your search, you can simply use $. Keyup () in this field, and then pass the data to the header filter. Thus, you get all the possibilities to use filters at the column level, even if you do not want to use them in this particular case.