summaryrefslogblamecommitdiffstats
path: root/webapp/src/components/SelectBox.vue
blob: ff3a2f939b85c8e423a552ab7fc1dd181b1c97b7 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14













                   




























                                                                                                                                                                                 
              
                 
              







                                         
         














                                                                                                
                     


               












                                              
                       


                  
                       


















                     













                    
      



                       


                    



            

                     





                                               


                                              






                                                                                















                                                                                                     











                                               






                                                                                               












                                                                   
                   


                      




                           

                       
                   



                          



                     


                     
          
           

 


                          
              

 









                                 
                      


                      
                    

 


                     

        
<i18n>
{
  "en": {
    "name": "Name",
    "more": "more"
  },
  "de": {
    "name": "Name",
    "more": "mehr"
  }
}
</i18n>

<template>
  <v-input class="v-text-field v-text-field--is-booted select-input" :class="inputClasses" :hide-details="hideDetails" :prepend-icon="prependIcon" :value="value" :rules="rules">
    <v-menu v-model="menu" offset-y :close-on-content-click="false" class="select-menu non-selectable" :nudge-bottom="1">
      <template #activator="{ on }">
        <div v-on="on" class="select-input-content">
          <label v-if="label !== undefined && (!singleLine || (singleLine && value.length === 0))"
            class="select-label v-label"
            :class="labelClasses"
          >{{ label }}</label>
          <div class="select-input-chips">
            <v-tooltip v-for="(item, index) in firstValueItems" :key="item[idKey]" top open-delay="800">
              <template #activator="{ on }">
                <v-chip
                  v-on="on"
                  class="item-chip ma-1"
                  :style="{ width: 'calc(' + (100 / maxColumns) + '% - 8px)' }"
                  small
                  close
                  @click:close="removeItem(index)"
                >
                  <div class="select-input-chip-text">{{ item.name || item.id }}</div>
                </v-chip>
              </template>
              <span>{{ item.name || item.id }}</span>
            </v-tooltip>
            <span v-if="value.length > maxShow" class="text--secondary and-more" :style="{ width: 'calc(' + (100 / maxColumns) + '% - 8px)' }">
              + {{ value.length - maxShow }} {{ $t('more') }}
            </span>
          </div>
          <v-icon class="select-input-arrow" :class="{ 'expand-arrow-flipped': menu }" :color="menu ? 'primary' : ''">arrow_drop_down</v-icon>
        </div>
      </template>
      <v-card>
        <data-table
          :value="value"
          @input="$emit('input', $event)"
          :headers="headers"
          :items="items"
          slim
          :row-count="6"
          :single-select="singleSelect"
        >
          <template #[textKey]="row">
            <v-tooltip top>
              <template v-slot:activator="{ on }">
                <span style="font-size: 10px; opacity: 0.6" v-on="on">
                  {{ pathPreview(row.item.paths) }}
                </span>
              </template>
              <div style="font-size: 11px" v-for="(path, index) in row.item.paths" :key="index">
                {{ formatPath(path) }}
              </div>
            </v-tooltip>
            <div>
              {{ row.item.name }}
            </div>
          </template>
        </data-table>
      </v-card>
    </v-menu>
  </v-input>
</template>

<script>
import DataTable from '@/components/DataTable'

export default {
  name: 'SelectBox',
  components: {
    DataTable
  },
  props: {
    value: {
      type: Array,
      default: () => []
    },
    items: {
      type: Array,
      default: () => []
    },
    idKey: {
      type: String,
      default: 'id'
    },
    textKey: {
      type: String,
      default: 'name'
    },
    textHeading: {
      type: String
    },
    maxColumns: {
      type: Number,
      default: 2
    },
    maxRows: {
      type: Number,
      default: 3
    },
    label: {
      type: String
    },
    hideDetails: {
      type: Boolean,
      default: false
    },
    singleLine: {
      type: Boolean,
      default: false
    },
    prependIcon: {
      type: String
    },
    rules: {
      type: Array,
      default: () => []
    },
    singleSelect: {
      type: Boolean,
      default: false
    }
  },
  data () {
    return {
      menu: false,
      listView: false
    }
  },
  computed: {
    maxShow () {
      return this.maxColumns * this.maxRows - 1
    },
    firstValueItems () {
      return this.value.slice(0, this.maxShow)
    },
    computedTextHeading () {
      return this.textHeading === undefined ? this.$t('name') : this.textHeading
    },
    headers () {
      return [
        { key: this.textKey, text: this.computedTextHeading }
      ]
    },
    inputClasses () {
      const classes = {}
      if (this.menu) {
        classes['primary--text'] = true
        classes['v-input--is-label-active'] = true
        classes['v-input--is-focused'] = true
      }
      if (this.singleLine) classes['single-line'] = true
      return classes
    },
    labelClasses () {
      const classes = {}
      if (!this.menu || this.singleLine) classes['text--secondary'] = true
      if (!this.singleLine && (this.menu || this.value.length > 0)) classes['v-label--active'] = true
      return classes
    }
  },
  watch: {
    value () {
      window.dispatchEvent(new Event('resize'))
    }
  },
  methods: {
    removeItem (index) {
      const newValue = this.value.slice(0)
      newValue.splice(index, 1)
      this.$emit('input', newValue)
    },
    formatPath (path) {
      return path.join(' > ')
    },
    pathPreview (paths) {
      if (paths.length > 0) return paths[0].slice(Math.max(paths[0].length - 2, 0)).join(' > ')
      return ''
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.select-menu {
  width: 100%;
  overflow: hidden;
}

.select-input {
  margin-bottom: 0;
  padding-bottom: 1px;
}

.select-input.single-line {
  margin-top: 0;
  padding-top: 0;
}

.select-input-content {
  width: 100%;
  min-height: 32px;
  display: flex;
  align-items: flex-start;
}

.select-input-arrow {
  margin-top: 4px
}

.select-input-chips {
  display: flex;
  flex-wrap: wrap;
  flex: 1;
  width: 0;
}

.select-input-chip-text {
  overflow: hidden;
  text-overflow: ellipsis;
  width: 100%;
}

.item-chip >>> .v-chip__content {
  width: 100%;
  cursor: pointer;
}

.expand-arrow-flipped {
  transform: rotate(180deg);
}

.and-more {
  white-space: nowrap;
  font-size: 13px;
  display: flex;
  align-items: center;
  padding: 4px 17px;
}

.select-label {
  position: absolute;
  left: 0;
}
</style>