Search code examples
vue.jsfilternestedlodashchildren

Filter on nested (recursive) data ( Vue 2 )


Here is an example of my JSON data :

"data":[  
  {  
     "id":01,
     "name":"test",
     "parent_id":null,
     "children":[  
        {  
           "id":15,
           "name":"subChild",
           "parent_id":21,
           "children":[  
              {  
                 "id":148,
                 "name":"subSubChild",
                 "parent_id":22,
                 "children":[  
                        ....
                 ]
              }
           ]
        }
     ]
  },

I would like to filter this level by level. I have made this method :

computed: {
      filteredData: function () {
        let filterData = this.filter.toLowerCase()
        return _.pickBy(this.data, (value, key) => {
          return _.startsWith(value.name.toLowerCase(), filterData)
        })
      },

This work for only the first "level" and I tried several solutions but none worked for children.

So, I would like to be able to filter by several levels.

If you have an idea! Thank you


Solution

  • A recursive function could come in handy for this particular purpose.

    Try the following approach, and for better view, click on Full page link next to the Run code snippet button down below.

    new Vue({
      el: '#app',
    
      data() {
        return {
          filter: '',
          maintainStructure: false,
          data: [{
            "id": 01,
            "name": "test",
            "parent_id": null,
            "children": [{
              "id": 15,
              "name": "subChild",
              "parent_id": 21,
              "children": [
                {
                  "id": 148,
                  "name": "subSubChild",
                  "parent_id": 22,
                  "children": []
                }, 
                {
                  "id": 150,
                  "name": "subSubChild3",
                  "parent_id": 24,
                  "children": []
                }
              ]
            }]
          }]
        }
      },
    
      methods: {
        $_find(items, predicate) {
          let matches = [];
    
          for (let item of items) {
            if (predicate(item)) {
              matches.push(item);
            } 
            else if (item.children.length) {
              let subMatches = this.$_find(item.children, predicate);
              
              if (subMatches.length) {
                if (this.maintainStructure) {
                  matches.push({
                    ...item,
                    children: subMatches
                  });
                }
                else {
                  matches.push(subMatches);
                }
              }
            }
          }
    
          return matches;
        },
    
        filterBy(item) {
          return item.name.toLowerCase().startsWith(this.filter.toLowerCase());
        }
      },
    
      computed: {
        filteredData() {
          return this.$_find(this.data, this.filterBy);
        }
      }
    })
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    
    <div id="app">
      <div>
        <label>Filter by <code>item.name</code>:</label>
        <input v-model.trim="filter" placeholder="e.g. subsub" />
      </div>
      
      <div>
        <label>
          <input type="checkbox" v-model="maintainStructure" /> Maintain structure
        </label>
      </div>
      
      <hr />
      
      <pre>{{filteredData}}</pre>
    </div>

    Note that I'm prefixing the function with $_ to sort of mark it as private function (as recommended in this Style Guide) since we're not going to invoke it anywhere else.