Scroll with arrow keys on dropdown – vuejs
Posted By: Anonymous
I’m building an autocomplete component in vuejs.
And at the moment I got stuck in the scroll animation.
The purpose is to click on the arrow keys to scroll as the direction of the key, but the scroll is only executed when the option is not visible.
I wanted something of this kind, but in vue / javascript. – http://jsfiddle.net/kMzR9/3/
If you can not see the problem in the example I left here since the screen is small, here is the jsfiddle – https://jsfiddle.net/v7yd94r5/
Here is an example of what I have.
const Autocomplete = {_x000D_
name: "autocomplete",_x000D_
template: "#autocomplete",_x000D_
props: {_x000D_
items: {_x000D_
type: Array,_x000D_
required: false,_x000D_
default: () => []_x000D_
},_x000D_
isAsync: {_x000D_
type: Boolean,_x000D_
required: false,_x000D_
default: false_x000D_
}_x000D_
},_x000D_
_x000D_
data() {_x000D_
return {_x000D_
isOpen: false,_x000D_
results: [],_x000D_
search: "",_x000D_
isLoading: false,_x000D_
arrowCounter: 0_x000D_
};_x000D_
},_x000D_
_x000D_
methods: {_x000D_
onChange() {_x000D_
// Let's warn the parent that a change was made_x000D_
this.$emit("input", this.search);_x000D_
_x000D_
// Is the data given by an outside ajax request?_x000D_
if (this.isAsync) {_x000D_
this.isLoading = true;_x000D_
} else {_x000D_
// Let's search our flat array_x000D_
this.filterResults();_x000D_
this.isOpen = true;_x000D_
}_x000D_
},_x000D_
_x000D_
filterResults() {_x000D_
// first uncapitalize all the things_x000D_
this.results = this.items.filter(item => {_x000D_
return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;_x000D_
});_x000D_
},_x000D_
setResult(result) {_x000D_
this.search = result;_x000D_
this.isOpen = false;_x000D_
},_x000D_
onArrowDown(evt) {_x000D_
if (this.arrowCounter < this.results.length) {_x000D_
this.arrowCounter = this.arrowCounter + 1;_x000D_
}_x000D_
},_x000D_
onArrowUp() {_x000D_
if (this.arrowCounter > 0) {_x000D_
this.arrowCounter = this.arrowCounter - 1;_x000D_
}_x000D_
},_x000D_
onEnter() {_x000D_
this.search = this.results[this.arrowCounter];_x000D_
this.isOpen = false;_x000D_
this.arrowCounter = -1;_x000D_
},_x000D_
showAll() {_x000D_
this.isOpen = !this.isOpen;_x000D_
(this.isOpen) ? this.results = this.items : this.results = [];_x000D_
},_x000D_
handleClickOutside(evt) {_x000D_
if (!this.$el.contains(evt.target)) {_x000D_
this.isOpen = false;_x000D_
this.arrowCounter = -1;_x000D_
}_x000D_
}_x000D_
},_x000D_
watch: {_x000D_
items: function(val, oldValue) {_x000D_
// actually compare them_x000D_
if (val.length !== oldValue.length) {_x000D_
this.results = val;_x000D_
this.isLoading = false;_x000D_
}_x000D_
}_x000D_
},_x000D_
mounted() {_x000D_
document.addEventListener("click", this.handleClickOutside);_x000D_
},_x000D_
destroyed() {_x000D_
document.removeEventListener("click", this.handleClickOutside);_x000D_
}_x000D_
};_x000D_
_x000D_
new Vue({_x000D_
el: "#app",_x000D_
name: "app",_x000D_
components: {_x000D_
autocomplete: Autocomplete_x000D_
}_x000D_
});
_x000D_
#app {_x000D_
font-family: "Avenir", Helvetica, Arial, sans-serif;_x000D_
-webkit-font-smoothing: antialiased;_x000D_
-moz-osx-font-smoothing: grayscale;_x000D_
color: #2c3e50;_x000D_
}_x000D_
_x000D_
.autocomplete {_x000D_
position: relative;_x000D_
width: 130px;_x000D_
}_x000D_
_x000D_
.autocomplete-results {_x000D_
padding: 0;_x000D_
margin: 0;_x000D_
border: 1px solid #eeeeee;_x000D_
height: 120px;_x000D_
overflow: auto;_x000D_
width: 100%;_x000D_
}_x000D_
_x000D_
.autocomplete-result {_x000D_
list-style: none;_x000D_
text-align: left;_x000D_
padding: 4px 2px;_x000D_
cursor: pointer;_x000D_
}_x000D_
_x000D_
.autocomplete-result.is-active,_x000D_
.autocomplete-result:hover {_x000D_
background-color: #4aae9b;_x000D_
color: white;_x000D_
}
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>_x000D_
<div id="app">_x000D_
<autocomplete :items="[ 'Apple', 'Banana', 'Orange', 'Mango', 'Pear', 'Peach', 'Grape', 'Tangerine', 'Pineapple']" />_x000D_
_x000D_
</div>_x000D_
_x000D_
<script type="text/x-template" id="autocomplete">_x000D_
<div class="autocomplete">_x000D_
<input type="text" @input="onChange" v-model="search" @keyup.down="onArrowDown" @keyup.up="onArrowUp" @keyup.enter="onEnter" @click="showAll" />_x000D_
<ul id="autocomplete-results" v-show="isOpen" class="autocomplete-results">_x000D_
<li class="loading" v-if="isLoading">_x000D_
Loading results..._x000D_
</li>_x000D_
<li v-else v-for="(result, i) in results" :key="i" @click="setResult(result)" class="autocomplete-result" :class="{ 'is-active': i === arrowCounter }">_x000D_
{{ result }}_x000D_
</li>_x000D_
</ul>_x000D_
_x000D_
</div>_x000D_
</script>
_x000D_
_x000D_
_x000D_
Solution
You need a function to check the position of the current element and move the scroll container if needed, there’s also a problem with the arrowDown function:
<ul ... ref="scrollContainer" ... >
...
<li ref="options" ... >
...
</ul>
onArrowDown(ev) {
ev.preventDefault()
if (this.arrowCounter < this.results.length-1) { <--- HERE NEED -1
this.arrowCounter = this.arrowCounter + 1;
this.fixScrolling();
}
},
onArrowUp(ev) {
ev.preventDefault()
if (this.arrowCounter > 0) {
this.arrowCounter = this.arrowCounter - 1;
this.fixScrolling()
}
},
fixScrolling(){
const liH = this.$refs.options[this.arrowCounter].clientHeight;
this.$refs.scrollContainer.scrollTop = liH * this.arrowCounter;
},
const Autocomplete = {_x000D_
name: "autocomplete",_x000D_
template: "#autocomplete",_x000D_
props: {_x000D_
items: {_x000D_
type: Array,_x000D_
required: false,_x000D_
default: () => Array(150).fill().map((_, i) => `Fruit ${i+1}`)_x000D_
},_x000D_
isAsync: {_x000D_
type: Boolean,_x000D_
required: false,_x000D_
default: false_x000D_
}_x000D_
},_x000D_
_x000D_
data() {_x000D_
return {_x000D_
isOpen: false,_x000D_
results: [],_x000D_
search: "",_x000D_
isLoading: false,_x000D_
arrowCounter: 0_x000D_
};_x000D_
},_x000D_
_x000D_
methods: {_x000D_
onChange() {_x000D_
// Let's warn the parent that a change was made_x000D_
this.$emit("input", this.search);_x000D_
_x000D_
// Is the data given by an outside ajax request?_x000D_
if (this.isAsync) {_x000D_
this.isLoading = true;_x000D_
} else {_x000D_
// Let's search our flat array_x000D_
this.filterResults();_x000D_
this.isOpen = true;_x000D_
}_x000D_
},_x000D_
_x000D_
filterResults() {_x000D_
// first uncapitalize all the things_x000D_
this.results = this.items.filter(item => {_x000D_
return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;_x000D_
});_x000D_
},_x000D_
setResult(result, i) {_x000D_
this.arrowCounter = i;_x000D_
this.search = result;_x000D_
this.isOpen = false;_x000D_
},_x000D_
onArrowDown(ev) {_x000D_
ev.preventDefault()_x000D_
if (this.arrowCounter < this.results.length-1) {_x000D_
this.arrowCounter = this.arrowCounter + 1;_x000D_
this.fixScrolling();_x000D_
}_x000D_
},_x000D_
onArrowUp(ev) {_x000D_
ev.preventDefault()_x000D_
if (this.arrowCounter > 0) {_x000D_
this.arrowCounter = this.arrowCounter - 1;_x000D_
this.fixScrolling()_x000D_
}_x000D_
},_x000D_
fixScrolling(){_x000D_
const liH = this.$refs.options[this.arrowCounter].clientHeight;_x000D_
this.$refs.scrollContainer.scrollTop = liH * this.arrowCounter;_x000D_
},_x000D_
onEnter() {_x000D_
this.search = this.results[this.arrowCounter];_x000D_
this.isOpen = false;_x000D_
this.arrowCounter = -1;_x000D_
},_x000D_
showAll() {_x000D_
this.isOpen = !this.isOpen;_x000D_
(this.isOpen) ? this.results = this.items : this.results = [];_x000D_
},_x000D_
handleClickOutside(evt) {_x000D_
if (!this.$el.contains(evt.target)) {_x000D_
this.isOpen = false;_x000D_
this.arrowCounter = -1;_x000D_
}_x000D_
}_x000D_
},_x000D_
watch: {_x000D_
items: function(val, oldValue) {_x000D_
// actually compare them_x000D_
if (val.length !== oldValue.length) {_x000D_
this.results = val;_x000D_
this.isLoading = false;_x000D_
}_x000D_
}_x000D_
},_x000D_
mounted() {_x000D_
document.addEventListener("click", this.handleClickOutside);_x000D_
},_x000D_
destroyed() {_x000D_
document.removeEventListener("click", this.handleClickOutside);_x000D_
}_x000D_
};_x000D_
_x000D_
new Vue({_x000D_
el: "#app",_x000D_
name: "app",_x000D_
components: {_x000D_
autocomplete: Autocomplete_x000D_
}_x000D_
});
_x000D_
#app {_x000D_
font-family: "Avenir", Helvetica, Arial, sans-serif;_x000D_
-webkit-font-smoothing: antialiased;_x000D_
-moz-osx-font-smoothing: grayscale;_x000D_
color: #2c3e50;_x000D_
}_x000D_
_x000D_
.autocomplete {_x000D_
position: relative;_x000D_
width: 130px;_x000D_
}_x000D_
_x000D_
.autocomplete-results {_x000D_
padding: 0;_x000D_
margin: 0;_x000D_
border: 1px solid #eeeeee;_x000D_
height: 120px;_x000D_
overflow: auto;_x000D_
width: 100%;_x000D_
}_x000D_
_x000D_
.autocomplete-result {_x000D_
list-style: none;_x000D_
text-align: left;_x000D_
padding: 4px 2px;_x000D_
cursor: pointer;_x000D_
}_x000D_
_x000D_
.autocomplete-result.is-active,_x000D_
.autocomplete-result:hover {_x000D_
background-color: #4aae9b;_x000D_
color: white;_x000D_
}
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>_x000D_
<div id="app">_x000D_
<autocomplete />_x000D_
_x000D_
</div>_x000D_
_x000D_
<script type="text/x-template" id="autocomplete">_x000D_
<div class="autocomplete">_x000D_
<input type="text" @input="onChange" v-model="search" @keyup.down="onArrowDown" @keyup.up="onArrowUp" @keyup.enter="onEnter" @click="showAll" />_x000D_
<ul id="autocomplete-results" v-show="isOpen" ref="scrollContainer" class="autocomplete-results">_x000D_
<li class="loading" v-if="isLoading">_x000D_
Loading results..._x000D_
</li>_x000D_
<li ref="options" v-else v-for="(result, i) in results" :key="i" @click="setResult(result, i)" class="autocomplete-result" :class="{ 'is-active': i === arrowCounter }">_x000D_
{{ result }}_x000D_
</li>_x000D_
</ul>_x000D_
_x000D_
</div>_x000D_
</script>
_x000D_
_x000D_
_x000D_
Answered By: Anonymous
Disclaimer: This content is shared under creative common license cc-by-sa 3.0. It is generated from StackExchange Website Network.