3

I'm new in vue.js, please help. I need filters in my table. Each column must have multiple select filter by values. When we click on column header - dropdown multi select opens, and we can select filtered values. For example like this Vuetify Data Table Inline Filter but this example doesn't work with vuetify 2. enter image description here

My html:

<template>
 <v-card class="elevation-3">
         <v-card-title>
           {{ other_title }}
           <v-btn style="background-color: white; box-shadow: none;" @click="csvExport(other_title, otherIncidentsData)">
               CSV<i class="fas fa-file-csv"></i>
           </v-btn>
           <v-btn text @click="exportToPdf()">
              Pdf<v-icon>mdi-file-pdf-box-outline</v-icon>
            </v-btn>
            <v-btn text @click="showExportModal">
              <v-icon>mdi-email-receive-outline</v-icon>
            </v-btn>
           <v-spacer></v-spacer>
           <v-text-field
             v-model="search"
             append-icon="mdi-magnify"
             label="Search"
             single-line
             hide-details
           ></v-text-field>
         </v-card-title>
         <v-data-table
           :search="search"
           :headers="headers"
           :items="otherIncidents"
           :items-per-page="10"
         >
          <template v-slot:item.priority.name="{ item }">
              <v-chip :color="getPriorityColor(item.priority.name)" dark>{{ item.priority.name }}</v-chip>
          </template>
         </v-data-table>
        </v-card>
</template>

And JS:

 export default {
      mixins: [
        mixin
      ],
      data() {
        return {
          search: '',
          title: 'MediaMyne reports',
          project_title: 'PROJECTS (open at the end of the reporting period)',
          new_title: 'NEW REQUESTS (created during the reporting period)',
          other_title: 'OTHER REQUESTS (remaining open or changed during the reporting period)',
          tabs: [
            { name: 'Projects' },
            { name: 'New Requests' },
            { name: 'Other Requests' },
          ],
          headers: [
            {
              text: 'Company', align: 'start', sortable: true, value: 'customer.name', width: '14%',
            },
            {
              text: 'Name (Costumer Contact)', align: 'start', sortable: true, value: 'reported_By_Customer_Contact.name', width: '16%',
            },
            {
              text: 'Title', align: 'start', sortable: true, value: 'name', width: '17%',
            },
            {
              text: 'Days open', align: 'center', sortable: true, value: 'daysOpen', width: '9%',
            },
            {
              text: 'Days waiting', align: 'center', sortable: true, value: 'daysWaiting', width: '10%',
            },
            {
              text: 'Workflow step', align: 'start', sortable: true, value: 'workflow_Step.name', width: '12%',
            },
            {
              text: 'Support type', align: 'start', sortable: true, value: 'custom_Fields.customFields.custom_266', width: '12%',
            },
            {
              text: 'Priority', align: 'start', sortable: true, value: 'priority.name', width: '10%',
            },
          ],
          otherIncidents: [],
          newIncidents: [],
          projectIncidents: [],
          activeTab: 0,
          pdfReportTitle: ''
        };
      },
      components: {
        ExportModal
      },
      computed: {
      ...mapGetters([
        'isLoggedIn'
      ]),
      otherIncidentsData() {
          return this.otherIncidents.map(item => ({
            Company: item.customer.name,
            Costumer_contact_name: item.reported_By_Customer_Contact.name,
            Title: item.name,
            Days_open: item.daysOpen,
            Days_waiting: item.daysWaiting,
            Workflow_step: item.workflow_Step.name,
            Support_type: item.custom_Fields.customFields.custom_266,
            Priority: item.priority.name
          }));
        },
      methods: {
        ...mapActions([
          'setLoginState'
        ]),
        getPriorityColor(priority) {
          switch (priority.toLowerCase()) {
            case 'critical':
              return '#fc0000';
            case 'high':
              return '#c20202';
            case 'normal':
              return '#dd7417';
            case 'low':
              return '#318d14';
            default:
              return 'rgb(0,0,0,0)';
          }
         }
    };
Vitalii Feduniak
  • 113
  • 1
  • 2
  • 8

1 Answers1

4

I'm making this on codepen these days, since I haven't found any nice example:

https://codepen.io/manuel-84/pen/NWxLLmN (work in progress)

new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data: () => ({
    dialog: false,
    filters: { 'name': [], 'calories': [], 'status': [] },
    activeFilters: {},
    desserts: [],
    editedIndex: -1,
    editedItem: {
      name: '',
      calories: 0,
      fat: 0,
      carbs: 0,
      protein: 0,
    },
    defaultItem: {
      name: '',
      calories: 0,
      fat: 0,
      carbs: 0,
      protein: 0,
    },
  }),

  computed: {
    headers () {
      return [
        {
          text: 'Dessert (100g serving)',
          align: 'start',
          sortable: true,
          value: 'name',
          filter: value => {
            return this.activeFilters.name.includes(value);
          }
        },
        { text: 'Calories', value: 'calories',
          filter: value => {
            return this.activeFilters.calories.includes(value);
          }
        },
        { text: 'Status', value: 'status',
          filter: value => {
            return this.activeFilters.status.includes(value);
          }
        },
        { text: 'Fat (g)', value: 'fat' },
        { text: 'Carbs (g)', value: 'carbs' },
        { text: 'Protein (g)', value: 'protein' },
        { text: 'Actions', value: 'actions', sortable: false },
      ]
    },
    formTitle () {
      return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
    },
  },

  watch: {
    dialog (val) {
      val || this.close()
    },
    /*filters: {
      deep: true,
      handler(val) {
        console.log(val)
      }
    }*/
  },

  created () {
    this.initialize()
  },

  methods: {
    initialize () {
      this.desserts = [
        {
          name: 'Frozen Yogurt',
          calories: 159,
          fat: 6.0,
          carbs: 24,
          protein: 4.0,
          status: 'DIET'
        },
        {
          name: 'Ice cream sandwich',
          calories: 237,
          fat: 9.0,
          carbs: 37,
          protein: 4.3,
          status: 'NO DIET'
        },
        {
          name: 'Eclair',
          calories: 262,
          fat: 16.0,
          carbs: 23,
          protein: 6.0,
          status: 'DIET'
        },
        {
          name: 'Cupcake',
          calories: 305,
          fat: 3.7,
          carbs: 67,
          protein: 4.3,
          status: 'NO DIET'
        },
        {
          name: 'Gingerbread',
          calories: 356,
          fat: 16.0,
          carbs: 49,
          protein: 3.9,
          status: 'DIET'
        },
        {
          name: 'Jelly bean',
          calories: 375,
          fat: 0.0,
          carbs: 94,
          protein: 0.0,
          status: 'NO DIET'
        },
        {
          name: 'Lollipop',
          calories: 392,
          fat: 0.2,
          carbs: 98,
          protein: 0,
          status: 'NO DIET'
        },
        {
          name: 'Honeycomb',
          calories: 408,
          fat: 3.2,
          carbs: 87,
          protein: 6.5,
          status: 'NO DIET'
        },
        {
          name: 'Donut',
          calories: 452,
          fat: 25.0,
          carbs: 51,
          protein: 4.9,
          status: 'DIET'
        },
        {
          name: 'KitKat',
          calories: 518,
          fat: 26.0,
          carbs: 65,
          protein: 7,
          status: 'NO DIET'
        },
      ];
      for (col in this.filters) {
        this.filters[col] = this.desserts.map((d) => { return d[col] }).filter(
          (value, index, self) => { return self.indexOf(value) === index }
        );
      }
      this.activeFilters = Object.assign({}, this.filters)
    },
    
    toggleAll (col) {
      this.activeFilters[col] = this.desserts.map((d) => { return d[col] }).filter(
        (value, index, self) => { return self.indexOf(value) === index }
      )
    },
    
    clearAll (col) {
      this.activeFilters[col] = []
    },

    editItem (item) {
      this.editedIndex = this.desserts.indexOf(item)
      this.editedItem = Object.assign({}, item)
      this.dialog = true
    },

    deleteItem (item) {
      const index = this.desserts.indexOf(item)
      confirm('Are you sure you want to delete this item?') && this.desserts.splice(index, 1)
    },

    close () {
      this.dialog = false
      this.$nextTick(() => {
        this.editedItem = Object.assign({}, this.defaultItem)
        this.editedIndex = -1
      })
    },

    save () {
      if (this.editedIndex > -1) {
        Object.assign(this.desserts[this.editedIndex], this.editedItem)
      } else {
        this.desserts.push(this.editedItem)
      }
      this.close()
    },
    
  },
  
})
.v-list--dense .v-list-item, .v-list-item--dense {
  min-height: 20px !important;
  height: 2rem;
}

.v-application--is-ltr .v-list-item__action:first-child, .v-application--is-ltr .v-list-item__icon:first-child {
  margin-right: .5rem !important;
}
<script src="https://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.3.4/dist/vuetify.min.js"></script>

<link href="https://cdn.jsdelivr.net/npm/vuetify@2.3.4/dist/vuetify.min.css" rel="stylesheet"/>
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css?family=Material+Icons" rel="stylesheet"/>

<span id="app">
  <v-app id="inspire">
    <v-data-table :headers="headers" :items="desserts" sort-by="calories" item-key="name" show-select>
      <template v-for="(col, i) in filters" v-slot:[`header.${i}`]="{ header }">
        <div style="display: inline-block; padding: 16px 0;">{{ header.text }}</div>
        <div style="float: right; margin-top: 8px">
          <v-menu :close-on-content-click="false" :nudge-width="200" offset-y transition="slide-y-transition" left fixed style="position: absolute; right: 0">
            <template v-slot:activator="{ on, attrs }">
              <v-btn color="indigo" icon v-bind="attrs" v-on="on">
                <v-icon small 
                  :color="activeFilters[header.value].length < filters[header.value].length ? 'red' : 'default'">
                  mdi-filter-variant
                </v-icon>
              </v-btn>
            </template>
            <v-list flat dense class="pa-0">
              <v-list-item-group multiple v-model="activeFilters[header.value]" class="py-2">
                <template v-for="(item, i) in filters[header.value]">
                  <v-list-item :key="`item-${i}`" :value="item" :ripple="false">
                    <template v-slot:default="{ active, toggle }">
                      <v-list-item-action>
                        <v-checkbox :input-value="active" :true-value="item"
                          @click="toggle" color="primary" dense></v-checkbox>
                      </v-list-item-action>
                      <v-list-item-content>
                        <v-list-item-title v-text="item"></v-list-item-title>
                      </v-list-item-content>
                    </template>
                  </v-list-item>
                </template>
              </v-list-item-group>
              <v-divider></v-divider>
              <v-btn text block @click="toggleAll(header.value)">Toggle all</v-btn>
              <v-btn text block @click="clearAll(header.value)">Clear all</v-btn>
            </v-list>
          </v-menu>
        </div>
      </template>
      <template v-slot:header="{ props: { headers } }">
        <thead>
          <tr>
            <th :colspan="headers.length">
              This is a header
            </th>
          </tr>
        </thead>
      </template>
      <template v-slot:top>
        <v-toolbar flat color="white">
          <v-toolbar-title>My CRUD</v-toolbar-title>
          <v-divider class="mx-4" inset vertical></v-divider>
          <v-spacer></v-spacer>
          <v-dialog v-model="dialog" max-width="500px">
            <template v-slot:activator="{ on, attrs }">
              <v-btn color="primary" dark class="mb-2" v-bind="attrs" v-on="on">New Item</v-btn>
            </template>
            <v-card>
              <v-card-title>
                <span class="headline">{{ formTitle }}</span>
              </v-card-title>
              <v-card-text>
                <v-container>
                  <v-row>
                    <v-col cols="12" sm="6" md="4">
                      <v-text-field v-model="editedItem.name" label="Dessert name"></v-text-field>
                    </v-col>
                    <v-col cols="12" sm="6" md="4">
                      <v-text-field v-model="editedItem.calories" label="Calories"></v-text-field>
                    </v-col>
                    <v-col cols="12" sm="6" md="4">
                      <v-text-field v-model="editedItem.fat" label="Fat (g)"></v-text-field>
                    </v-col>
                    <v-col cols="12" sm="6" md="4">
                      <v-text-field v-model="editedItem.carbs" label="Carbs (g)"></v-text-field>
                    </v-col>
                    <v-col cols="12" sm="6" md="4">
                      <v-text-field v-model="editedItem.protein" label="Protein (g)"></v-text-field>
                    </v-col>
                  </v-row>
                </v-container>
              </v-card-text>
              <v-card-actions>
                <v-spacer></v-spacer>
                <v-btn color="blue darken-1" text @click="close">Cancel</v-btn>
                <v-btn color="blue darken-1" text @click="save">Save</v-btn>
              </v-card-actions>
            </v-card>
          </v-dialog>
        </v-toolbar>
      </template>
      <template v-slot:item.actions="{ item }">
        <v-icon small class="mr-2" @click="editItem(item)">
          mdi-pencil
        </v-icon>
        <v-icon small @click="deleteItem(item)">
          mdi-delete
        </v-icon>
      </template>
      <template v-slot:no-data>
        <v-btn color="primary" @click="initialize">Reset</v-btn>
      </template>
    </v-data-table>
  </v-app>
  </div>

I hope the code is clear, anyway feel free to ask

manuel-84
  • 2,582
  • 1
  • 23
  • 23
  • 1
    Those filters are gorgeous! Thanks for the example! – mmcfly Mar 12 '21 at 21:01
  • Any news on updating this example ? I have some issues regarding this when I'm trying to use a list of objects instead of a simple list of strings .. In my situation I'm not using in headers value: "string" but instead I'm using value: "object.property" – ricristian May 20 '21 at 02:17
  • The best version I found so far. I had an issue, clicking on the filtering options, and removed the toggle function and now it works flawlessly – Stéphane Gerber Sep 07 '21 at 03:26