summaryrefslogblamecommitdiffstats
path: root/webapp/src/components/ComponentSearchTable.vue
blob: 35046d76364846e6c2f0e79cc72b574bd8886315 (plain) (tree)





















                                                                               
                                                                           








                                                                                          
                                                                         




                                                                          

                         



                                                                     


                                                                                         
                                                          












                                                                                                       



                             
                        







                                                   

                                                                          
                                                                                                                                            









                               







                    



                       







                          



                          





                       
                                      





                     


                         


             

                                                                             

                                                          
                                                                                                           

                                           



                              
                              


                              
















                                                                        




                                                                        











                                                                                                                                      










                                                                                                                    
                                                                          



                                                                                                     




                                                             













                                                                   
                          


















                                                          
        
<i18n>
{
  "en": {
    "search": "Search",
    "all": "All",
    "pageText": "{0}-{1} of {2}",
    "pageTextZero": "0 of 0",
    "rowsPerPageText": "Rows per page:"
  },
  "de": {
    "search": "Suche",
    "all": "Alle",
    "pageText": "{0}-{1} von {2}",
    "pageTextZero": "0 von 0",
    "rowsPerPageText": "Reihen pro page:"
  }
}
</i18n>

<template>
  <div>
    <v-layout v-if="actionsNeeded" wrap align-center class="actions-container">
      <v-flex md3 sm5 xs12 order-md1 order-sm1 order-xs1 class="text-left">
        <v-text-field
          class="search-field"
          :placeholder="$t('search')"
          v-model="search"
          hide-details
          prepend-inner-icon="search"
        ></v-text-field>
      </v-flex>
      <v-flex md4 sm12 xs12 offset-md1 offset-sm0 offset-xs0 order-md2 order-sm3 order-xs3
              class="text-md-center text-right caption font-weight-thin">
        {{ $t('rowsPerPageText') }}
        <v-select
          class="rows-per-page-select body-1"
          v-model="pagination.rowsPerPage"
          :items="rowsPerPageItems.concat({ text: $t('all'), value: -1 })"
          color="primary"
          hide-details
          :menu-props="{
            offsetY: '',
            contentClass: 'search-table-rows-per-page-select-content'
          }"
        ></v-select>
      </v-flex>
      <v-flex md4 sm6 xs12 offset-md0 offset-sm1 offset-xs0 order-md3 order-sm2 order-xs2
              class="text-right caption font-weight-thin">
        <span class="page-text">{{
          pagination.totalItems > 0 ? $t('pageText', [
            (pagination.page - 1) * pagination.rowsPerPage + 1,
            Math.min(pagination.page * pagination.rowsPerPage, pagination.totalItems),
            pagination.totalItems
          ]) : $t('pageTextZero')
        }}</span>
        <v-btn class="page-button" icon @click="prevPage"><v-icon>keyboard_arrow_left</v-icon></v-btn>
        <v-btn class="page-button" icon @click="nextPage"><v-icon>keyboard_arrow_right</v-icon></v-btn>
      </v-flex>
    </v-layout>
    <v-divider v-if="actionsNeeded"></v-divider>
    <v-data-table
      :headers="headers"
      :items="items"
      :select-all="selectAll"
      :item-key="itemKey"
      :loading="loading"
      v-bind="dataTableProps"
      :value="value"
      @input="$emit('input', $event)"
      hide-actions
      :search="search"
      :pagination.sync="pagination"
      :custom-filter="customFilter"
      @click.native.capture.passive="setShiftState"
    >
      <template v-if="$scopedSlots.items" slot="items" slot-scope="props">
        <slot name="items" :data="props" :color="props.selected && { backgroundColor: $vuetify.theme.currentTheme.primary + '11' }" ></slot>
      </template>
    </v-data-table>
  </div>
</template>

<script>

export default {
  name: 'ComponentSearchTable',
  props: {
    headers: {
      type: Array,
      required: true
    },
    items: {
      type: Array,
      required: true
    },
    value: {
      type: Array,
      default: () => []
    },
    selectAll: {
      type: Boolean,
      default: () => false
    },
    itemKey: {
      type: String,
      default: () => 'id'
    },
    loading: {
      type: Boolean,
      default: () => false
    },
    dataTableProps: {
      type: Object,
      default: () => {}
    },
    rowsPerPageItems: {
      type: Array,
      default: () => [10, 25, 50, 100]
    }
  },
  data () {
    return {
      search: '',
      pagination: {},
      displayedItems: [],
      lastSelectId: null,
      shiftKey: false
    }
  },
  computed: {
    headersValues () { return this.headers.map(x => x.value) },
    headerCount () { return this.headers.length + (this.selectAll ? 1 : 0) },
    rowsPerPage () { return this.pagination.rowsPerPage },
    actionsNeeded () {
      return this.items && this.rowsPerPageItems.length > 0 && this.items.length > this.rowsPerPageItems[0]
    },
    page () { return this.pagination.page }
  },
  watch: {
    rowsPerPage () {
      this.pagination.page = 1
      this.lastSelectId = null
    },
    items () {
      this.pagination.page = 1
      this.lastSelectId = null
    },
    page (value) {
      this.lastSelectId = null
    },
    displayedItems () {
      this.lastSelectId = null
    },
    value (newValue, oldValue) {
      const changedCount = newValue.length - oldValue.length
      var doSelect
      var startId = null
      if (this.shiftKey !== null && changedCount === 1) {
        if (this.shiftKey) startId = this.lastSelectId
        this.lastSelectId = newValue.find(x => !oldValue.includes(x)).id
        doSelect = true
      } else if (this.shiftKey !== null && changedCount === -1) {
        if (this.shiftKey) startId = this.lastSelectId
        this.lastSelectId = oldValue.find(x => !newValue.includes(x)).id
        doSelect = false
      }
      if (startId !== null) {
        var inRange = false
        var tmp = doSelect ? [...this.value] : []
        for (var i = 0; i < this.displayedItems.length; i++) {
          if (!inRange && (this.displayedItems[i].id === startId || (this.displayedItems[i].id === this.lastSelectId))) inRange = true
          else if (inRange && (this.displayedItems[i].id === startId || (this.displayedItems[i].id === this.lastSelectId))) break
          if (inRange && (!doSelect || !tmp.includes(this.displayedItems[i]))) tmp.push(this.displayedItems[i])
        }
        if (!doSelect || !tmp.includes(this.displayedItems[i])) tmp.push(this.displayedItems[i])
        if (!doSelect) tmp = this.value.filter(x => !tmp.includes(x))
        this.shiftKey = null
        this.$emit('input', tmp)
      }
    }
  },
  methods: {
    prevPage () {
      if (this.pagination.page > 1) this.pagination.page -= 1
    },
    nextPage () {
      if (this.pagination.page * this.pagination.rowsPerPage < this.pagination.totalItems) this.pagination.page += 1
    },
    customFilter (items, search) {
      search = String(search).toLowerCase().trim()
      this.displayedItems = search === '' ? items : items.filter(item => {
        return Object.keys(item).some(key => {
          return this.headersValues.includes(key) && String(item[key]).toLowerCase().includes(search)
        })
      })
      this.pagination.totalItems = this.displayedItems.length
      return this.displayedItems
    },
    setShiftState (event) {
      this.shiftKey = event.shiftKey
    }
  },
  created () {
    this.pagination.rowsPerPage = this.rowsPerPageItems[0] || -1
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.actions-container {
  padding: 20px;
}
.search-field {
  margin: 0 10px 5px 10px;
}
.rows-per-page-select {
  display: inline-block;
  margin: 0 10px 0 20px;
  width: min-content;
}
.page-text {
  margin-right: 20px;
}
.page-button {
  margin-top: 0;
  margin-bottom: 0;
}
</style>

<style>
.search-table-rows-per-page-select-content .v-list__tile {
  height: 38px;
}
</style>