|
@@ -0,0 +1,120 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="dropdown" :class="{'open': open}">
|
|
|
|
|
+ <div class="input-group">
|
|
|
|
|
+ <input type="text" class="form-control" v-model="searchText" @input="searchChanged">
|
|
|
|
|
+ <div class="form-group-append">
|
|
|
|
|
+ <a class="toggle input-group-text bg-primary text-light" @mousedown.prevent @click="setOpen(!open)">
|
|
|
|
|
+ <span class="arrow-up">▲</span>
|
|
|
|
|
+ <span class="arrow-down">▼</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <ul class="suggestion-list list-unstyled">
|
|
|
|
|
+ <li v-for="(suggestion,index) in matches" :key="index"
|
|
|
|
|
+ @mousedown.prevent
|
|
|
|
|
+ @click="suggestionSelected(suggestion)"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ suggestion[1] }}
|
|
|
|
|
+ </li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+<script>
|
|
|
|
|
+export default {
|
|
|
|
|
+ props: {
|
|
|
|
|
+ value: null,
|
|
|
|
|
+ // An object with the following format
|
|
|
|
|
+ // {
|
|
|
|
|
+ // key: value,
|
|
|
|
|
+ // key: value,
|
|
|
|
|
+ // }
|
|
|
|
|
+ options: {
|
|
|
|
|
+ type: Object,
|
|
|
|
|
+ required: true
|
|
|
|
|
+ },
|
|
|
|
|
+ allowCustom: {
|
|
|
|
|
+ type: Boolean,
|
|
|
|
|
+ default: false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ data () {
|
|
|
|
|
+ return {
|
|
|
|
|
+ searchText: '',
|
|
|
|
|
+ selectedOption: null,
|
|
|
|
|
+ open: false,
|
|
|
|
|
+ // allows for checking whether button was clicked or text was typed.
|
|
|
|
|
+ // If button was clicked, we show all options without having to clear
|
|
|
|
|
+ // out any searchText that was autofilled when page loaded.
|
|
|
|
|
+ isSearching: false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ matches () {
|
|
|
|
|
+ // If button was clicked, show all options, if typing, filter
|
|
|
|
|
+ // options based on searchText
|
|
|
|
|
+ return Object.entries(this.options).filter((option) => {
|
|
|
|
|
+ var optionText = option[1].toUpperCase()
|
|
|
|
|
+ if (this.isSearching) {
|
|
|
|
|
+ return optionText.match(this.searchText.toUpperCase())
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ return optionText.match('')
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ // if button is clicked, show all options
|
|
|
|
|
+ setOpen (isOpen) {
|
|
|
|
|
+ this.open = isOpen
|
|
|
|
|
+ this.isSearching = false
|
|
|
|
|
+ },
|
|
|
|
|
+ // if typing in the box, start filtering options
|
|
|
|
|
+ searchChanged () {
|
|
|
|
|
+ if (!this.open) {
|
|
|
|
|
+ this.open = true
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.isSearching) {
|
|
|
|
|
+ this.isSearching = true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // If custom values are allowed, emits {id: 'custom', name: searchtext}
|
|
|
|
|
+ if (this.allowCustom) {
|
|
|
|
|
+ this.$emit('input', {'id': 'custom', 'name': this.searchText})
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // emits { id: suggestion_id, name: suggestion_name} to v-model when
|
|
|
|
|
+ // a suggestion is selected
|
|
|
|
|
+ suggestionSelected (suggestion) {
|
|
|
|
|
+ this.open = false
|
|
|
|
|
+ this.searchText = suggestion[1]
|
|
|
|
|
+ this.$emit('input', { 'id': suggestion[0], 'name': suggestion[1]})
|
|
|
|
|
+ },
|
|
|
|
|
+ updateComponentWithValue(newValue) {
|
|
|
|
|
+ if (this.allowCustom) {
|
|
|
|
|
+ this.searchText = this.value.name
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ if (newValue.id > -1) {
|
|
|
|
|
+ // Find the matching text for the supplied option value
|
|
|
|
|
+ for (var id in this.options) {
|
|
|
|
|
+ if (this.options.hasOwnProperty(id)) {
|
|
|
|
|
+ if (this.options[id] === this.options[newValue.id]) {
|
|
|
|
|
+ this.searchText = this.options[id]
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted () {
|
|
|
|
|
+ this.updateComponentWithValue(this.value)
|
|
|
|
|
+ },
|
|
|
|
|
+ watch: {
|
|
|
|
|
+ value: function (newValue) {
|
|
|
|
|
+ this.updateComponentWithValue(newValue)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|