journal-intime/resources/js/components/Autocomplete.vue

195 lines
6.3 KiB
Vue
Raw Normal View History

<template>
<div class="autocomplete">
<label :for="id" v-if="hasLabel">{{label}}</label>
<textarea v-if="textarea" :id="id" :name="name" :rows="rows" :cols="cols" class="autocomplete-input" :placeholder="placeholder"
@focusout="focusout" @focus="focus" @keydown.13="chooseItem" @keydown.tab="chooseItem"
@keydown.40="moveDown" @keydown.38="moveUp" v-model="inputValue" type="text"></textarea>
<input v-else :id="id" :name="name" class="autocomplete-input" :placeholder="placeholder" @focusout="focusout" @focus="focus"
@keydown.13="chooseItem" @keydown.tab="chooseItem" @keydown.40="moveDown" @keydown.38="moveUp"
v-model="inputValue" type="text">
<ul :class="{'autocomplete-list': true, [id+'-list']: true}" v-if="searchMatch.length > 0">
<li :class="{active: selectedIndex === index}" v-for="(result, index) in searchMatch"
@click="selectItem(index), chooseItem()" v-html="highlightWord(result)">
</li>
</ul>
</div>
</template>
<script>
// https://codepen.io/ph1p/pen/GEJYBZ
const getCaretCoordinates = require('textarea-caret');
const standardItems = [
"#Fitness",
"#Humeur"
];
export default {
name: "Autocomplete",
props: ["items", "placeholder", "label", "textarea", "rows", "cols", "hasLabel", "name"],
data() {
return {
id: 'input-' + (Math.random() * 1000 | 0),
inputValue: "",
searchMatch: [],
selectedIndex: 0,
clickedChooseItem: false,
wordIndex: 0
};
},
mounted() {
const _self = this;
document.querySelector('#' + this.id)
.addEventListener('input', function() {
const caret = getCaretCoordinates(this, this.selectionEnd);
if (_self.searchMatch.length > 0) {
const element = document.querySelectorAll('.' + _self.id + '-list');
if (element[0]) {
element[0].style.top = caret.top + 40 + 'px';
element[0].style.left = caret.left + 'px';
}
}
});
},
computed: {
listToSearch() {
if (typeof this.items !== "undefined" && this.items.length > 0) {
return this.items;
} else {
return standardItems;
}
},
currentWord() {
return this.inputValue.replace(/(\r\n|\n|\r)/gm, ' ').split(' ')[this.wordIndex];
},
inputSplitted() {
return this.inputValue.replace(/(\r\n|\n|\r)/gm, ' ').split(" ");
}
},
watch: {
inputValue() {
this.focus();
this.selectedIndex = 0;
this.wordIndex = this.inputSplitted.length - 1;
}
},
methods: {
highlightWord(word) {
const regex = new RegExp("(" + this.currentWord + ")", "g");
return word.replace(regex, '<mark>$1</mark>');
},
setWord(word) {
let currentWords = this.inputValue.replace(/(\r\n|\n|\r)/gm, '__br__ ').split(' ');
currentWords[this.wordIndex] = currentWords[this.wordIndex].replace(this.currentWord, word + ' ');
this.wordIndex += 1;
this.inputValue = currentWords.join(' ').replace(/__br__\s/g, '\n');
},
moveDown() {
if (this.selectedIndex < this.searchMatch.length - 1) {
this.selectedIndex++;
}
},
moveUp() {
if (this.selectedIndex !== -1) {
this.selectedIndex--;
}
},
selectItem(index) {
this.selectedIndex = index;
this.chooseItem();
},
chooseItem(e) {
this.clickedChooseItem = true;
if (this.selectedIndex !== -1 && this.searchMatch.length > 0) {
if (e) {
e.preventDefault();
}
this.setWord(this.searchMatch[this.selectedIndex]);
this.selectedIndex = -1;
}
},
focusout(e) {
setTimeout(() => {
if (!this.clickedChooseItem) {
this.searchMatch = [];
this.selectedIndex = -1;
}
this.clickedChooseItem = false;
}, 100);
},
focus() {
this.searchMatch = [];
if (this.currentWord !== "") {
this.searchMatch = this.listToSearch.filter(
el => el.indexOf(this.currentWord) === 0
);
}
if (
this.searchMatch.length === 1 &&
this.currentWord === this.searchMatch[0]
) {
this.searchMatch = [];
}
}
}
}
</script>
<style scoped>
.autocomplete {
position: relative;
}
.autocomplete label {
display: block;
margin-bottom: 5px;
font-size: 14px;
font-weight: 100;
}
.autocomplete-input {
padding: 7px 10px;
width: 93%;
border: 1px solid #ddd;
border-radius: 4px;
outline: none;
}
.autocomplete-input:focus {
border-color: #000;
}
.autocomplete-list {
position: absolute;
z-index: 2;
overflow: auto;
min-width: 250px;
max-height: 150px;
margin: 0;
margin-top: 5px;
padding: 0;
border: 1px solid #eee;
list-style: none;
border-radius: 4px;
background-color: #fff;
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.05);
}
li {
margin: 0;
padding: 8px 15px;
border-bottom: 1px solid #f5f5f5;
}
li:last-child {
border-bottom: 0;
}
li:hover, li.active {
background-color: #f5f5f5;
}
</style>