Skip to content
Snippets Groups Projects
Forked from GéoContrib / Géocontrib Frontend
827 commits behind the upstream repository.
Dropdown.vue 4.71 KiB
<template>
  <div
    :id="`custom-dropdown${identifier}`"
    :class="[
      'ui search selection dropdown',
      { 'active visible': isOpen },
      { disabled },
    ]"
    @click="toggleDropdown"
  >
    <input
      v-if="search"
      ref="input"
      v-model="input"
      class="search"
      autocomplete="off"
      tabindex="0"
      :placeholder="placehold"
      @input="isOpen = true"
      @keyup.enter="select(0)"
      @keyup.esc="toggleDropdown(false)"
    >
    <div
      v-if="!input"
      class="default text"
    >
      <div v-if="Array.isArray(selected)">
        <span v-if="selected[0]"> {{ selected[0] }} - </span>
        <span class="italic">{{ selected[1] }}</span>
      </div>
      <div v-else>
        {{ selected }}
      </div>
    </div>
    <i
      :class="['dropdown icon', { clear: clearable && selected }]"
      aria-hidden="true"
      @click="clear"
    />
    <div :class="['menu', { 'visible transition': isOpen }]">
      <div
        v-for="(option, index) in filteredOptions || ['No results found.']"
        :id="option.name && Array.isArray(option.name) ? option.name[0] : option.name || option"
        :key="option + index"
        :class="[
          filteredOptions ? 'item' : 'message',
          { 'active selected': option.name === selected },
        ]"
        @click="select(index)"
      >
        <div v-if="option.name && Array.isArray(option.name)">
          <span v-if="option.name[0]"> {{ option.name[0] }} - </span>
          <span class="italic">{{ option.name[1] }}</span>
        </div>
        <span v-else-if="option.name">
          {{ option.name }}
        </span>
        <span v-else>
          {{ option }}
        </span>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Dropdown',

  props: {
    clearable: {
      type: Boolean,
      default: null,
    },
    disabled: {
      type: Boolean,
      default: null,
    },
    options: {
      type: Array,
      default: null,
    },
    placeholder: {
      type: String,
      default: null,
    },
    selected: {
      type: [String, Array],
      default: null,
    },
    search: {
      type: Boolean,
      default: null,
    },
  },

  data() {
    return {
      isOpen: false,
      input: '',
      identifier: 0,
    };
  },

  computed: {
    filteredOptions: function () {
      let options = this.options;
      if (this.search && this.input !== '') {
        options = this.options.filter(this.matchInput);
      }
      return options.length > 0 ? options : null;
    },

    placehold() {
      return this.input ? '' : this.placeholder;
    },
  },

  created() {
    const crypto = window.crypto || window.msCrypto;
    const array = new Uint32Array(1);
    this.identifier = Math.floor(crypto.getRandomValues(array) * 10000);
    window.addEventListener('mousedown', this.clickOutsideDropdown);
  },

  beforeDestroy() {
    window.removeEventListener('mousedown', this.clickOutsideDropdown);
  },

  methods: {
    toggleDropdown(val) {
      if (this.isOpen) { //* if dropdown is open :
        this.input = ''; // * -> clear input field when closing dropdown
      } else if (this.search) { //* if dropdown is closed is a search dropdown:
        this.$refs.input.focus({ //*  -> focus on input field
          preventScroll: true,
        });
      } else if (this.clearable && val.target && this.selected) {
        this.clear(); //* clear selected and input
      }
      this.isOpen = typeof val === 'boolean' ? val : !this.isOpen;
    },

    select(index) {
      // * toggle dropdown is called several time, timeout delay this function to be the last
      setTimeout(() => {
        this.isOpen = false;
      }, 0);
      if (this.filteredOptions) {
        this.$emit('update:selection', this.filteredOptions[index]);
      }
      this.input = '';
    },

    matchInput(el) {
      let match;
      if (el.name && Array.isArray(el.name)) {
        match =
          el.name[0].toLowerCase().includes(this.input.toLowerCase()) ||
          el.name[1].toLowerCase().includes(this.input.toLowerCase());
      } else {
        match = el.name
          ? el.name.toLowerCase().includes(this.input.toLowerCase())
          : el.toLowerCase().includes(this.input.toLowerCase());
      }
      return match;
    },

    clear() {
      if (this.clearable) {
        this.input = '';
        this.$emit('update:selection', '');
        if (this.isOpen) {
          this.toggleDropdown(false);
        }
      }
    },

    clickOutsideDropdown(e) {
      if (!e.target.closest(`#custom-dropdown${this.identifier}`) && this.isOpen) {
        this.toggleDropdown(false);
      }
    },
  },
};
</script>

<style scoped>
.ui.selection.dropdown .menu > .item {
  white-space: nowrap;
}
.italic {
  font-style: italic;
}
</style>