Vue select-search
Vue select-search is a component that allows you to search select options. Items component receives must be an array of objects, preferably containing id key. Uses Vue, Lodash, Bootstrap 4 styling and few FontAwesome 4 icons
html
<div id='app'>
<div class='container'>
<h1>Vue <strong><select-search></strong> component</h1>
<p>Use arrow keys (up and down) on the input field to switch between previous and next item</p>
<p>Hit enter on the input field to select, or use your mouse to manually select from select box</p>
<select-search v-model='firstExample' :items='ppl' :shows='"name"'></select-search>
<p>Result: {{ firstExample }}</p>
<p>First option doesn't necessarily have to be disabled</p>
<select-search v-model='secondExample' :items='ppl' :shows='"name"' :first-disabled='false' :first-label='"ALL"'></select-search>
<p>Result: {{ secondExample }}</p>
<p>Button at the end to make it feel like an inline form</p>
<select-search v-model='thirdExample' :items='ppl' :shows='"name"' :add-btn='true' @selected='thirdExampleSelected'></select-search>
<p>Smashing enter on the input or clicking the button emits an 'selected' event, so you can listen for it on the parent</p>
<p>Customize which item key is returned as a result (default: id)</p>
<select-search v-model='fourthExample' :items='ppl' :shows='"name"' :returns='"name"'></select-search>
<p>Result: {{ fourthExample }}</p>
</div>
</div>
<template id='my-template'>
<div class='input-group col' style='padding:0'>
<div class='input-group-prepend'>
<span class='input-group-text'><i class='fa fa-search'></i> {{ getCount() }}</span>
</div>
<input type='text' class='form-control'
v-model='searchQuery'
placeholder='Search'
@keyup.enter='addItem'
@keyup.up='prevItem'
@keyup.down='nextItem'
@keyup.esc='close'
>
<select class='form-control d-inline' v-model='selectedItem' @change='manualChange'>
<option :disabled='firstDisabled' value=0>
<template v-if='addedItem == 0 && !firstDisabled'>=></template> {{ firstLabel }}
</option>
<option v-for='item in searchedItems' :key='item.id' :value='item[returns]'>
<template v-if='addedItem == item[returns]'>=></template> {{ formOptionText(item) }}
</option>
</select>
<div class='input-group-append' v-if='addBtn'>
<button class='btn btn-primary' @click='addItem'><i class='fa fa-chevron-right'></i></button>
</div>
</div>
</template>
Css
*
outline: none
box-sizing: border-box
.container
padding-top: 20px
JavaScript
Vue.component('select-search', {
template: '#my-template',
props: {
value: {
required: true
},
items: {
type: Array,
required: true
},
returns: {
type: String,
default: 'id'
},
shows: {
type: String,
required: true
},
firstLabel: {
type: String,
default: 'Please select'
},
firstDisabled: {
type: Boolean,
default: true
},
search: {
type: String,
default: ''
},
addBtn: {
type: Boolean,
default: false
}
},
data() {
return {
searchQuery: '',
addedItem: this.value || 0,
selectedItem: this.value || 0
}
},
computed: {
columns() {
if(!this.items.length) return []
return Object.keys(this.items[0])
},
searchColumns() {
if(!this.search.length) return this.columns
return this.search.split('|')
},
searchedItems() {
if(!this.searchQuery) {
this.selectedItem = 0
if(this.addedItem !== 0 && this.checkItemExists(this.addedItem)) this.selectedItem = this.addedItem
else this.addedItem = 0
return this.items
}
let items = this.items.filter(item => {
return this.searchColumns.some((column) => {
return String(item[column]).toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1
})
})
if(items.length) this.selectedItem = items[0][this.returns]
else this.selectedItem = 0
return items
}
},
methods: {
checkItemExists(item) {
return _.map(this.items, this.returns).indexOf(item) > -1
},
addItem() {
//if(this.selectedItem == 0) return
this.addedItem = this.selectedItem
if(this.addedItem == 0 && this.firstDisabled) return
this.$emit('input', this.addedItem)
this.$emit('selected')
},
prevItem() {
if(!this.items.length) return
let i = this.searchedItems.map(itm => itm[this.returns]).indexOf(this.selectedItem)
if(i == 0) {
if(this.firstDisabled) this.selectedItem = this.searchedItems[this.searchedItems.length - 1][this.returns]
else this.selectedItem = 0
} else if(this.selectedItem == 0) this.selectedItem = this.searchedItems[this.searchedItems.length - 1][this.returns]
else this.selectedItem = this.searchedItems[i - 1][this.returns]
},
nextItem() {
if(!this.items.length) return
let i = this.searchedItems.map(itm => itm[this.returns]).indexOf(this.selectedItem)
if(i == this.searchedItems.length - 1) {
if(this.firstDisabled) this.selectedItem = this.searchedItems[0][this.returns]
else this.selectedItem = 0
}
else this.selectedItem = this.searchedItems[i + 1][this.returns]
},
close() {
this.$emit('close ')
},
formOptionText(item) {
let j = []
let s = this.shows.split('|')
s.forEach(x => j.push(item[x]))
return j.join(' | ')
},
getCount() {
return this.firstDisabled ? this.searchedItems.length : this.searchedItems.length + 1
},
manualChange() {
this.addedItem = this.selectedItem
this.$emit('input', this.addedItem)
}
}
});
const ppl = [
{
id: 1,
name: 'John Doe'
},
{
id: 2,
name: 'John Cena'
},
{
id: 3,
name: 'Sylvester Stallone'
}
]
new Vue({
el: '#app',
data: {
ppl: ppl,
firstExample: 0,
secondExample: 0,
thirdExample: 0,
fourthExample: 0
},
methods: {
thirdExampleSelected() {
alert('Result: ' + this.thirdExample)
}
}
});
Author
Goran Petričević
Demo
See the Pen Vue select-search by Goran Petričević (@goranp) on CodePen.