10

I have a table generated with bootstrap-vue that shows the results of a system search.

The Results Table shows the records to the user, and the user can sort them and filter them.

How can I add the search field underneath the table header <th> generated with the bootstrap-vue <b-table> element?

Screenshot of the current table: enter image description here

Mockup of the wanted table: Mockup of the wanted table

Adriano
  • 3,788
  • 5
  • 32
  • 53

4 Answers4

27

You can use the top-row slot to customise your own first-row. See below for a bare-bones example.

new Vue({
  el: '#app',
  data: {
    filters: {
      id: '',
      issuedBy: '',
      issuedTo: ''
    },
    items: [{id:1234,issuedBy:'Operator',issuedTo:'abcd-efgh'},{id:5678,issuedBy:'User',issuedTo:'ijkl-mnop'}]
  },
  computed: {
    filtered () {
      const filtered = this.items.filter(item => {
        return Object.keys(this.filters).every(key =>
            String(item[key]).includes(this.filters[key]))
      })
      return filtered.length > 0 ? filtered : [{
        id: '',
        issuedBy: '',
        issuedTo: ''
      }]
    }
  }
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css"/><link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css"/><script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script><script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script><script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>

<div id="app">
<b-table striped show-empty :items="filtered">
  <template slot="top-row" slot-scope="{ fields }">
    <td v-for="field in fields" :key="field.key">
      <input v-model="filters[field.key]" :placeholder="field.label">
    </td>
  </template>
</b-table>
</div>

Note: I've used a computed property to filter the items instead of the :filter prop in b-table because it doesn't render rows if all the items are filtered out, including your custom first-row. This way I can provide a dummy data row if the result is empty.

Adriano
  • 3,788
  • 5
  • 32
  • 53
Phil
  • 157,677
  • 23
  • 242
  • 245
  • 1
    Thanks a lot. It works perfectly. The only disadvantage was that it messed up my table. My table was completely resized. To solve this I added to my input class="col-sm" – Nemo May 08 '19 at 14:45
  • When I copied the same code, it gives me the template root disallows 'v-for' directives. I'm unable to solve the problem. I'm new to vue. Please help me @Phil – prabna Mar 02 '20 at 10:06
  • @Palistha sounds like you've got a `v-for` on a template root element. Without seeing your code, I can't really help but that doesn't appear in my answer anywhere – Phil Mar 03 '20 at 04:15
  • @Palistha you're best posting a new question – Phil Mar 03 '20 at 13:46
  • @Phil is there a way to do this with a dynamically generated `filters` object that is created at runtime? I tried setting `filters` to `{}` in the data section and then iterating over the first object in the `items` array in a `mounted()`. The filters object is setup correctly, but `filtered()` never runs when I type something into the inputs. – Sam Oct 07 '20 at 18:19
  • Nevermind @Phil, I found a solution. I was iterating over the first element of the items array and doing the following to initialize the `filters` object: `this.filters[key] = '';`. I changed that line of code to `Vue.set(this.filters, key, '');` per a comment here: https://forum.vuejs.org/t/computed-property-not-updating/21148/18 – Sam Oct 07 '20 at 18:54
2

Have upvoted phil's answer, just making it more generic

filtered() {
      const filtered = this.items.filter(item => {
        return Object.keys(this.filters).every(key =>
          String(item[key]).includes(this.filters[key])
        );
      });
      return filtered.length > 0
        ? filtered
        : [
            Object.keys(this.items[0]).reduce(function(obj, value) {
              obj[value] = '';
              return obj;
            }, {})
          ];
    }
Abhishek
  • 21
  • 2
0

Thanks to you for these useful answers. It saved some of my time today. However, in case items are given asynchronously i had to add a test on items size like this

filtered() {
                if (this.items.length > 0) {
                    const filtered = this.items.filter(item => {
                        return Object.keys(this.filters).every(key => String(item[key]).includes(this.filters[key])
                        );
                    });
                    return filtered.length > 0
                        ? filtered
                        : [
                            Object.keys(this.items[0]).reduce(function (obj, value) {
                                obj[value] = '';
                                return obj;
                            }, {})
                        ];
                }
            },

On another hand if needed to have column with no filter, i added this test below

In the template

      <td v-for="field in fields" :key="field.key">
          <input v-if="fieldIsFiltered(field)" v-model="filters[field.key]" :placeholder="field.label">
        </td>

and within component methods

fieldIsFiltered(field)  {
                return Object.keys(this.filters).includes(field.key)
            }
bdamay
  • 1
  • 1
-1

mistake

      const filtered = this.items.filter(item => {
        return Object.keys(this.filters).every(key =>
            // String(item[key]).includes(this.filters[key]))
            return String(item[key]).includes(this.filters[key]))
      })