Skip to content

Instantly share code, notes, and snippets.

@ammardev
Created May 24, 2022 19:15
Show Gist options
  • Save ammardev/8fe5ae9cd68da1f5e7fba14327dcb6c9 to your computer and use it in GitHub Desktop.
Save ammardev/8fe5ae9cd68da1f5e7fba14327dcb6c9 to your computer and use it in GitHub Desktop.
<script type="text/ecmascript">
import _ from 'lodash';
import moment from 'moment';
export default {
props: ['value', 'options', 'optionId', 'optionText'],
data() {
return {
searchTerm: '',
selectedOptionIndex: 0,
focused: false,
newOptions: []
}
},
mounted() {
this.$refs.input.style.width = this.value.length ? '25px' : '108px';
this.$refs.input.placeholder = this.value.length ? '' : 'Add tags';
},
watch: {
value(val) {
this.$refs.input.style.width = this.value.length ? '25px' : '108px';
this.$refs.input.placeholder = this.value.length ? '' : 'Add tags';
},
searchTerm(val) {
var width = val.length * 12;
this.$refs.input.style.width = width > 25 ? width + 'px' : '25px';
},
matches(val) {
this.selectedOptionIndex = _.find(val, option => option[this.optionId] == 'addNew') ? 1 : 0;
}
},
computed: {
matches() {
let options = _.union(this.options, this.newOptions);
if (!this.searchTerm) {
return _.reject(options, option => {
return _.find(this.value, {id: option.id});
});
} else {
var matches = _.reject(options, option => {
return _.find(this.value, {id: option.id}) ||
option[this.optionText].toLowerCase().indexOf(this.searchTerm.toLowerCase()) == -1;
});
let addNewOption = {};
addNewOption[this.optionText] = 'Add new';
addNewOption[this.optionId] = 'addNew';
matches.unshift(addNewOption);
return matches;
}
}
},
methods: {
/**
* Select the given option.
*/
selectOption(option) {
let values = this.value || [];
if (_.includes(values, option.id)) return;
if (option[this.optionId] == 'addNew') {
return this.addNewOption();
}
this.searchTerm = '';
values.push(option);
this.$emit('input', values);
},
/**
* Remove the given option.
*/
removeOption(option) {
let values = this.value || [];
values = _.reject(values, {id: option.id});
this.$emit('input', values);
},
/**
* Backspace was hit.
*/
backspaceAction() {
if (this.searchTerm) return;
let values = this.value || [];
values.pop();
this.$emit('input', values);
this.selectedOptionIndex = 0;
},
/**
* Select the next option.
*/
selectNextOption() {
if (!this.matches.length) return;
if (this.selectedOptionIndex + 1 == this.matches.length) return;
this.selectedOptionIndex = this.selectedOptionIndex + 1;
},
/**
* Select the previous option.
*/
selectPreviousOption() {
if (!this.matches.length) return;
if (this.selectedOptionIndex === 0) return;
this.selectedOptionIndex = this.selectedOptionIndex - 1;
},
/**
* Add the selected option to the list.
*/
addSelectedOption() {
if (!this.matches[this.selectedOptionIndex]) {
return this.addNewOption();
}
this.selectOption(
this.matches[this.selectedOptionIndex]
);
this.selectedOptionIndex = 0;
},
/**
* Add a brand new option.
*/
addNewOption() {
let values = this.value || [];
let option = {};
let existingOption = _.find(_.union(this.options, this.newOptions), option => {
return option.name.toLowerCase() == this.searchTerm.toLowerCase();
});
if (existingOption) {
this.searchTerm = '';
return;
}
option[this.optionText] = this.searchTerm;
option[this.optionId] = _.uniqueId();
values.push(option);
this.newOptions.push(option);
this.searchTerm = '';
this.$emit('input', values);
},
/**
* Activate the field.
*/
activate() {
this.focused = true;
this.$refs.input.focus();
},
/**
* Deactivate the field.
*/
deactivate() {
this.focused = false;
}
}
}
</script>
<template>
<div class="multiselect relative z-50"
:class="{active: focused}"
@click="activate"
v-click-outside="deactivate">
<div class="multiselect_options">
<span class="bg-light hover:bg-red rounded cursor-pointer text-sm text-contrast font-semibold px-1 mr-1"
v-for="option in value"
v-on:click="removeOption(option)">{{option[optionText]}}</span>
<input type="text"
class="focus:outline-none bg-transparent text-text-color"
v-on:keydown.8="backspaceAction"
v-on:keydown.40="selectNextOption"
v-on:keydown.38="selectPreviousOption"
v-on:keydown.enter="addSelectedOption"
ref="input"
v-model="searchTerm">
</div>
<div class="multiselect_dropdown absolute w-full" v-show="focused">
<button v-if="! matches.length" class="text-text-color">Add new tag...</button>
<button v-for="(match, index) in matches"
v-on:click="selectOption(match)"
:class="{selected: selectedOptionIndex == index}"
:value="match[optionId]">{{match[optionText]}}
</button>
</div>
</div>
</template>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment