summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue
blob: b1809e6a9f38e21d97515fb61a62de180adc50a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<script>
import { GlListbox } from '@gitlab/ui';
import { __ } from '~/locale';

const MIN_ITEMS_COUNT_FOR_SEARCHING = 20;

export default {
  i18n: {
    noResultsText: __('No results found'),
  },
  components: {
    GlListbox,
  },
  model: GlListbox.model,
  props: {
    name: {
      type: String,
      required: true,
    },
    defaultToggleText: {
      type: String,
      required: true,
    },
    selected: {
      type: String,
      required: false,
      default: null,
    },
    items: {
      type: GlListbox.props.items.type,
      required: true,
    },
  },
  data() {
    return {
      searchString: '',
    };
  },
  computed: {
    allOptions() {
      const allOptions = [];

      const getOptions = (options) => {
        for (let i = 0; i < options.length; i += 1) {
          const option = options[i];
          if (option.options) {
            getOptions(option.options);
          } else {
            allOptions.push(option);
          }
        }
      };
      getOptions(this.items);

      return allOptions;
    },
    isGrouped() {
      return this.items.some((item) => item.options !== undefined);
    },
    isSearchable() {
      return this.allOptions.length > MIN_ITEMS_COUNT_FOR_SEARCHING;
    },
    filteredItems() {
      const searchString = this.searchString.toLowerCase();

      if (!searchString) {
        return this.items;
      }

      if (this.isGrouped) {
        return this.items
          .map(({ text, options }) => {
            return {
              text,
              options: options.filter((option) => option.text.toLowerCase().includes(searchString)),
            };
          })
          .filter(({ options }) => options.length);
      }

      return this.items.filter((item) => item.text.toLowerCase().includes(searchString));
    },
    toggleText() {
      return this.selected
        ? this.allOptions.find((option) => option.value === this.selected).text
        : this.defaultToggleText;
    },
  },
  methods: {
    search(searchString) {
      this.searchString = searchString;
    },
  },
};
</script>

<template>
  <div>
    <gl-listbox
      :selected="selected"
      :toggle-text="toggleText"
      :items="filteredItems"
      :searchable="isSearchable"
      :no-results-text="$options.i18n.noResultsText"
      @search="search"
      @select="$emit($options.model.event, $event)"
    />
    <input ref="input" type="hidden" :name="name" :value="selected" />
  </div>
</template>