Search code examples
javascriptangularjsjsontreeviewangularjs-filter

AngularJS filter tree structure recursively by property


I tried multiple things now, but didn't had much success. Let's assume, that I have a list like this:

[
    {
        "name": "Africa",
        "translation": "Afrika",
        "someNum": 87207,
        "countries": [
            {
                "name": "Algeria",
                "translation": "Algerien",
                "someNum": 58020,
                "countries": null
            },
            {
                "name": "Morocco",
                "translation": "Marokko",
                "someNum": 27314,
                "countries": null
            },
            {
                "name": "Libya",
                "translation": "Lybien",
                "someNum": 77788,
                "countries": null
            },
            {
                "name": "Somalia",
                "translation": "Somalia",
                "someNum": 17945,
                "countries": null
            },
            {
                "name": "Kenya",
                "translation": "Kenia",
                "someNum": 12505,
                "countries": null
            },
            {
                "name": "Mauritania",
                "translation": "Mauretanien",
                "someNum": 25787,
                "countries": null
            },
            {
                "name": "South Africa",
                "translation": "S\u00fcdafrika",
                "someNum": 39997,
                "countries": null
            }
        ]
    },
    {
        "name": "America",
        "translation": "Amerika",
        "someNum": 48450,
        "countries": [
            {
                "name": "North America",
                "translation": "Nordamerika",
                "someNum": 23397,
                "countries": [
                    {
                        "name": "Canada",
                        "translation": "Kanada",
                        "someNum": 58709,
                        "countries": null
                    },
                    {
                        "name": "USA",
                        "translation": "USA",
                        "someNum": 64725,
                        "countries": [
                            {
                                "name": "New York",
                                "translation": "New York",
                                "someNum": 50202,
                                "countries": null
                            },
                            {
                                "name": "California",
                                "translation": "Kalifornien",
                                "someNum": 92801,
                                "countries": [
                                    {
                                        "name": "Los Angeles",
                                        "translation": "Los Angeles",
                                        "someNum": 78283,
                                        "countries": null
                                    },
                                    {
                                        "name": "San Diego",
                                        "translation": "San Diego",
                                        "someNum": 30373,
                                        "countries": null
                                    },
                                    {
                                        "name": "Sacramento",
                                        "translation": "Sacramento",
                                        "someNum": 50721,
                                        "countries": null
                                    },
                                    {
                                        "name": "San Francisco",
                                        "translation": "San Francisco",
                                        "someNum": 65772,
                                        "countries": null
                                    },
                                    {
                                        "name": "Bakersville",
                                        "translation": "Bakersville",
                                        "someNum": 20390,
                                        "countries": null
                                    }
                                ]
                            },
                            {
                                "name": "Lousiana",
                                "translation": "Lousiana",
                                "someNum": 26245,
                                "countries": null
                            },
                            {
                                "name": "Texas",
                                "translation": "Texas",
                                "someNum": 57720,
                                "countries": null
                            },
                            {
                                "name": "Nevada",
                                "translation": "Nevada",
                                "someNum": 41399,
                                "countries": null
                            },
                            {
                                "name": "Montana",
                                "translation": "Montana",
                                "someNum": 97221,
                                "countries": null
                            },
                            {
                                "name": "Virginia",
                                "translation": "Virginia",
                                "someNum": 64101,
                                "countries": null
                            }
                        ]
                    }
                ]
            },
            {
                "name": "Middle America",
                "translation": "Mittelamerika",
                "someNum": 60813,
                "countries": [
                    {
                        "name": "Mexico",
                        "translation": "Mexiko",
                        "someNum": 97953,
                        "countries": null
                    },
                    {
                        "name": "Honduras",
                        "translation": "Honduras",
                        "someNum": 40591,
                        "countries": null
                    },
                    {
                        "name": "Guatemala",
                        "translation": "Guatemala",
                        "someNum": 41592,
                        "countries": null
                    }
                ]
            },
            {
                "name": "South America",
                "translation": "S\u00fcdamerika",
                "someNum": 61507,
                "countries": null
            }
        ]
    },
    {
        "name": "Asia",
        "translation": "Asien",
        "someNum": 38179,
        "countries": [
            {
                "name": "China",
                "translation": "China",
                "someNum": 47266
            },
            {
                "name": "India",
                "translation": "Indien",
                "someNum": 33424,
                "countries": null
            },
            {
                "name": "Malaysia",
                "translation": "Malaysia",
                "someNum": 38010,
                "countries": null
            },
            {
                "name": "Thailand",
                "translation": "Thailand",
                "someNum": 41356,
                "countries": null
            },
            {
                "name": "Vietnam",
                "translation": "Vietnam",
                "someNum": 79489,
                "countries": null
            },
            {
                "name": "Singapore",
                "translation": "Singapur",
                "someNum": 53176,
                "countries": null
            },
            {
                "name": "Indonesia",
                "translation": "Indonesien",
                "someNum": 89895,
                "countries": null
            },
            {
                "name": "Mongolia",
                "translation": "Mongolei",
                "someNum": 42783,
                "countries": null
            }
        ]
    },
    {
        "name": "Europe",
        "translation": "Europa",
        "someNum": 84723,
        "countries": [
            {
                "name": "North",
                "translation": "Nord",
                "someNum": 16833,
                "countries": [
                    {
                        "name": "Norway",
                        "translation": "Norwegen",
                        "someNum": 11817,
                        "countries": null
                    },
                    {
                        "name": "Sweden",
                        "translation": "Schweden",
                        "someNum": 38210,
                        "countries": null
                    },
                    {
                        "name": "Finland",
                        "translation": "Finnland",
                        "someNum": 10669,
                        "countries": null
                    }
                ]
            },
            {
                "name": "East",
                "translation": "Ost",
                "someNum": 28434,
                "countries": [
                    {
                        "name": "Romania",
                        "translation": "Rum\u00e4nien",
                        "someNum": 45122,
                        "countries": null
                    },
                    {
                        "name": "Bulgaria",
                        "translation": "Bulgarien",
                        "countries": null
                    },
                    {
                        "name": "Poland",
                        "translation": "Polen",
                        "someNum": 84183,
                        "countries": null
                    }
                ]
            },
            {
                "name": "South",
                "translation": "S\u00fcd",
                "someNum": 71856,
                "countries": [
                    {
                        "name": "Italy",
                        "translation": "Italien",
                        "someNum": 76406,
                        "countries": null
                    },
                    {
                        "name": "Greece",
                        "translation": "Griechenland",
                        "someNum": 18189,
                        "countries": null
                    },
                    {
                        "name": "Spain",
                        "translation": "Spanien",
                        "someNum": 36148,
                        "countries": null
                    }
                ]
            },
            {
                "name": "West",
                "translation": "West",
                "someNum": 33487,
                "countries": [
                    {
                        "name": "France",
                        "translation": "Frankreich",
                        "someNum": 72622,
                        "countries": null
                    },
                    {
                        "name": "England",
                        "translation": "England",
                        "someNum": 51927,
                        "countries": null
                    },
                    {
                        "name": "Portugal",
                        "translation": "Portugal",
                        "someNum": 25502,
                        "countries": null
                    }
                ]
            }
        ]
    },
    {
        "name": "Oceania",
        "translation": "Ozeanien",
        "someNum": 69844,
        "countries": [
            {
                "name": "Australia",
                "translation": "Australien",
                "someNum": 17211,
                "countries": null
            },
            {
                "name": "New Zealand",
                "translation": "Neuseeland",
                "someNum": 87401,
                "countries": null
            }
        ]
    },
    {
        "name": "Arctica",
        "translation": "Arktis",
        "someNum": 67183,
        "countries": null
    },
    {
        "name": "Antarctica",
        "translation": "Antarktis",
        "someNum": 92518,
        "countries": null
    }
]

What I try to achieve, is to find all parents and/or children, matching my search criteria while keeping the whole tree intact.

Let's say I am searching for Australia as value of the property name. The expected result would be:

- Oceania
-- Australia

But what I get is

- Oceania
-- Australia
-- New Zealand

Another example: I am searching for Kalifornien as value of the property translation. The expected result would be:

- America
-- North America
--- USA
---- California
----- Bakersville
----- San Diego
----- Sacramento
----- San Francisco
----- Los Angeles

But the actual result is absolutely not, what you'd expect.

So the main problem I am experiencing is, that the original Angular filter doesn't process children correctly.

I have set up a Plunker to demonstrate the problem: https://plnkr.co/edit/3FavpC8XB3hTvT94fZli?p=preview

I can also change the format of the JSON data, if that makes it easier to achieve a proper filtering.

EDIT:

I've had partial success: https://plnkr.co/edit/lDoUK3?p=preview

There's just one problem left. If I search (for example) for California, the objects from the countries property of the object with the name California are missing. So the last item is California (which is good so far), but I also need Bakersville, San Diego & co. under it.


Solution

  • If you want to get the subItems of your selected element in the list, you should add the items whose parent (Or some parent-of-parent) must appear in the list after filtering. To do this, I would recommend you to create a recursive function:

    this.getSelectedElementList = function(list, element) {
      var result;
      for (var i in list) {
        if (list[i].name.toLowerCase().indexOf(element) > -1) {
          return list[i];
        } else {
          if (list[i].countries && list[i].countries.length) {
            result = this.getSelectedElementList(list[i].countries, element);
            if (typeof result !== 'undefined') {
              return result;
            }
          }
        }
      }
      return result;
    };
    

    And you can call it inside your preFilter function twice. The first time to get the parent object:

    var parentList = _self.getSelectedElementList(_self.listItems, _self.filterQuery.toLowerCase());
    

    And the second one to check that the current item is inside that object:

    if (typeof parentList !== 'undefined' && parentList.countries && parentList.countries.length && typeof _self.getSelectedElementList(parentList.countries, item.name.toLowerCase()) !== 'undefined') {
      path.push(item);
    }
    

    As you are currently filtering only by name, my proposal works this way. I have also updated your Plunkr