Skip to content
Fix Code Error

Scroll with arrow keys on dropdown – vuejs

March 24, 2021 by Code Error
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.

_x000D_

_x000D_

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;
},

_x000D_

_x000D_

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

Related Articles

  • Form field border-radius is not working only on the last…
  • VueJS masonry layout
  • Trouble with boxes appearing/hiding based on selection
  • DataTable draw daterange from vaadin-date-picker in polymer
  • render function or template not defined in component:…
  • How can I pass a wct test while rearranging children spans…
  • Autocomplete - select option with mouse click - vuejs
  • Why are CSS keyframe animations broken in Vue components…
  • What is causing this broken animation/transition in a Vue.js…
  • How would I be able to multiple select and pass data in the…

Disclaimer: This content is shared under creative common license cc-by-sa 3.0. It is generated from StackExchange Website Network.

Post navigation

Previous Post:

How can i get image height and image width with FileReader and VueJs?

Next Post:

How do I disable running an app on the local network when using vue-cli?

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • Get code errors & solutions at akashmittal.com
© 2022 Fix Code Error