Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filtering a multi-dimensional Knockout observableArray with ko.utils.arrayFilter()

I have an observableArray that looks something like this:

this.menuItems = ko.observableArray([
    { name: "level1", subItems: [
        { name: "level1-1" },
        { name: "level1-2" }
    ] },
    { name: "level2" },
    { name: "level3", subItems: [
        { name: "level3-1" },
        { name: "level3-2", subItems: [
            { name: "level3-2-1" }
        ] },
    ] },
    { name: "level4" }
]);

This renders a multi-level navigation menu. So some items can have subItems, others don't. And the number of levels is unknown.

Now I have a 'filter navigation' input to filter these things.

var self = this,
    menuFilter = ko.observable(""),
    filter = menuFilter().toLowerCase();

    if (filter === "") {
        return self.menuItems();
    } else {
        return ko.utils.arrayFilter(self.menuItems(), function (item) {
            if (item.name.toLowerCase().indexOf(filter) !== -1) {
                return true;
            }
        });
    }

This works great for top-level items, but I'm not sure the best way to loop through self.menuItems().subItems, and then then next level, and next, etc.

Any ideas?

edit: I just created this JS Fiddle and it seems to be working. Now I have to figure out how to get it going on my [slightly more complicated] app.

http://jsfiddle.net/KSrzL/7/

edit (again): My latest issue is that the top level has no information, so I have to START with .children, which isn't working.

http://jsfiddle.net/KSrzL/8/

like image 231
dmathisen Avatar asked Mar 03 '26 05:03

dmathisen


1 Answers

This seems to work: http://jsfiddle.net/MRtRm/

The key pieces:

self.nonNullItems = function(arr) {
    return arr.filter(function(x) { return x !== null; });
};

self.filteredObjectOrNull = function(obj, query) {
    if (obj.hasOwnProperty('children')) {
        var filteredChildren = self.nonNullItems(obj.children.filter( function(x) { return self.filteredObjectOrNull(x, query); }));
        if (filteredChildren.length > 0)
            return {name: obj.name, children: filteredChildren};
    }

    if (self.matchText(obj.name, query))
            return obj;

    return null;
};

self.filterItems = function() {
    var filter = self.itemFilter();

    if (filter === "") {
        return self.items();
    } else {
        return self.nonNullItems(self.items().map( function(x) { return self.filteredObjectOrNull(x, filter); }));
    }
}

I think it could be simplified in a couple of ways but it seems to pass the testing I did. (You should add a couple more levels of menu to make sure.)

like image 157
bdesham Avatar answered Mar 05 '26 18:03

bdesham