noxeone
noxeone

Reputation: 25

Vue.js Keys events Up, Down, Enter to control selection

I did a search in an array with a list output. In the list need to select a product. A mouse click event adds the selected item. And it is added to another array and then we are already working with it.

All perfectly!

But I need to add the ability to select using the keyboard keys Up, Down and Enter.

I can use the events "v-on: keyup.up" "v-on: keyup.down" "v-on: keyup.enter", but what to do inside methods?

Need: using the down and up keys, select the desired item, this item is highlighted using the "active" class. When press Enter, the element id value is transferred to the method. How to do it?

Template:

    <input class="form-control col-sm-4" type="text" v-model="searchGoods" placeholder="Search">
    <div v-if="searchGoods">
        <ul class="col-sm-4 list-group" v-if="goodsList.length">
            <li v-for="item in searchQuery" class="list-group-item list-group-item-action" @click="addGoods(item.id)">{{item.name}}</li>
        </ul>
    </div>

JS:

        data () {
        return {
          goodsList: [
            { id: '1', name: 'Cheese 1' },
            { id: '3', name: 'Cheese 2' },
            { id: '4', name: 'Cheese 3' },
            { id: '5', name: 'Meat' },
            { id: '7', name: 'Tomato' },
            { id: '11', name: 'Sauce' },
          ],
          searchGoods: ''
        }
    },
    computed: {
        searchQuery: function(){
            if (this.searchGoods){
                return this.goodsList.filter((item)=>{
                    return this.searchGoods.toLowerCase().split(' ').every(v => item.name.toLowerCase().includes(v))
                })}
            else{
                return this.goodsList;
            }
        },
    },
    methods: {
        addGoods(id){
            let goods=this.goodsList.find(item => item.id == id);
            this.products.push({
                id: id,
                goods_name: goods.name
            });
            this.searchGoods='';
        },
    }

Try to enter "Chee", what we see on HTML:

    <div>
    <input type="text" placeholder="Search" class="form-control col-sm-4"> 
        <div>
            <ul class="col-sm-4 list-group">
                <li class="list-group-item list-group-item-action">Cheese 1</li>
                <li class="list-group-item list-group-item-action">Cheese 2</li>
                <li class="list-group-item list-group-item-action">Cheese 3</li>
            </ul>
        </div>
    </div>

Upvotes: 1

Views: 2092

Answers (2)

nbixler
nbixler

Reputation: 522

So you already know you can do v-on:keyup.enter="(YourFunction())

From there, there are a few ways you could do it. One way would be to have a computed that tells you which option is active so you can grab the id from there.

Another option would be to have "currentlySelected" in data that gets set when something is chosen, then instead of addGoods(id) just have addGoods() and inside that method you grab this.currentlySelected and avoid needing to pass in id at all.

Upvotes: 0

J&#243;zef Podlecki
J&#243;zef Podlecki

Reputation: 11283

You have to track active item in the list as well as filtered items

Example

new Vue({
  data() {
    return {
      products: [],
      goodsList: [{
          id: '1',
          name: 'Cheese'
        },
        {
          id: '2',
          name: 'Meat'
        },
        {
          id: '3',
          name: 'Fruits'
        },
        {
          id: '4',
          name: 'Vegetables '
        },
        {
          id: '5',
          name: 'Sweets'
        },
        {
          id: '6',
          name: 'Furniture'
        },
        {
          id: '7',
          name: 'Fish'
        },
        {
          id: '8',
          name: 'Lamb'
        },
        {
          id: '9',
          name: 'Utensils'
        }
      ],
      filtered: [],
      active: {
        id: -1,
        index: 0
      },
      searchGoods: ''
    }
  },
  watch: {
    searchGoods: function() {
      if (!this.searchGoods) {
        this.filtered = [];
        return;
      }

      const normalized = this.searchGoods.toLowerCase();  
      this.filtered = this.goodsList.filter(({name}) => name.toLowerCase().includes(normalized))
      
      if(!this.filtered.length) {
        return;
      }
      
      const id = this.filtered[this.active.index].id;
      this.$set(this.active, 'id', id);
    }
  },
  created() {
    window.addEventListener('keydown', this.onKey)
  },
  beforeDestroy() {
    window.removeEventListener('keydown', this.onKey)
  },
  methods: {
    onKey(event) {
      const key = event.key;
      
      const map = {
        Enter: () => {
          this.addGoods(this.active.id);
        },
        ArrowDown: () => {
          const { active, filtered } = this;
          const index = (active.index + 1) % this.filtered.length;
          const id = filtered[index].id;
          
          this.active = {
            index,
            id
          };
        },
        ArrowUp: () => {
          const { active, filtered } = this;
          let index = active.index - 1;
          index = index < 0 ? filtered.length - 1 : index;
          const id = filtered[index].id;
          
          this.active = {
            index,
            id
          };
        }
      }
      
      const func = map[key];
      if(func) {
        func();
      }
    },
    addGoods(id) {
      let goods = this.goodsList.find(item => item.id == id);
      this.products.push({
        id: id,
        goods_name: goods.name
      });
      this.searchGoods = '';
      this.filtered = [];
    },
  },
  el: '#container',
});
.active {
  border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="container">
  <div>
    <input class="form-control col-sm-4" type="text" v-model="searchGoods" placeholder="Search">
    <div v-if="filtered.length">
      <ul class="col-sm-4 list-group" v-if="goodsList.length">
        <li :class="{'active': active.id === item.id}" v-for="item in filtered" class="list-group-item list-group-item-action" @click="addGoods(item.id)">{{item.name}}</li>
      </ul>
    </div>
    <div>Products: {{JSON.stringify(products)}}</div>
  </div>
</div>

Upvotes: 1

Related Questions