vuejs onblur directive not working as expected, while should hide the list

104 Views Asked by At

I'm trying to make a simple dropdown search select list. I'd like to hide the name list by clicking outside of it. And the issue is that when I click outside the list of search items by using @blur directive the appropriate list item doesn't fill the input field. It's assumingly because of the @click="selectCategory(category)" is triggered later than @blur="isVisible = false" in the template.

<template>
  <div сlass="search-bar" :style="{'position' : (isVisible) ? 'absolute' : 'fixed'}">
    <input 
    type="text" 
    v-model="input"  
    @focus="isVisible = true" 
    // @blur="isVisible = false"  doesn't work as required
    />
    <div class="search-bar-options" v-if="isVisible">
      <div v-for="category in filteredUser" :key="category.id" @click="selectCategory(category)"> 
        <p>{{ category.name }}</p>
      </div>
      <div v-if="filteredUser.length === 0">
        <p>No results found!</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      input: "",
      selectedItem: null,
      categoriesDynamic: [],
      isVisible: false,
    };
  },
  mounted() {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((json) => {
        this.categoriesDynamic = json;
      });
  },
    filteredUser() {
      const query = this.input.toLowerCase();
      if (this.input === "") {
        return this.categoriesDynamic;
      } else {
        return this.categoriesDynamic.filter(category => {
          return category.name.toLowerCase().includes(query);
        });
      }
    },
  },
  methods: {
    selectCategory(category) {
      this.input = category.name;
      this.isVisible = false;
    },
  },
};
</script>

<style scoped>
.pointer {
  cursor: pointer;
}
.show {
  visibility: show;
}
.hide {
  visibility: hidden;
}
</style>

1

There are 1 best solutions below

0
Neha Soni On

One solution could be to detect the clicked element. If the clicked element is not an input or a category element, then the dropdown can be closed.

Here is a working demo in which I gave a class "category" to each list item for element detecting purposes.

Vue.config.productionTip = false;

var app = new Vue({
  el: '#app',
  data() {
    return {
      input: "",
      selectedItem: null,
      categoriesDynamic: [],
      isVisible: false,
    };
  },
  mounted() {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((json) => {
        this.categoriesDynamic = json;
      });
  },
  created() {
    document.addEventListener('click', (e) => {
      let isInput = e.target instanceof HTMLInputElement;
      let isCategoryEl = e.target.classList.contains('category');
      if (isInput || isCategoryEl) return;
      this.isVisible = false;
    })
  },
  computed: {
    filteredUser() {
      const query = this.input.toLowerCase();
      if (this.input === "") {
        return this.categoriesDynamic;
      } else {
        return this.categoriesDynamic.filter(category => {
          return category.name.toLowerCase().includes(query);
        });
      }
    },
  },
  methods: {
    selectCategory(category) {
      this.input = category.name;
      this.isVisible = false;
    },
  },
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <div сlass="search-bar" :style="{'position' : (isVisible) ? 'absolute' : 'fixed'}">
    <input 
      type="text" 
      v-model="input"  
      @focus="isVisible = true" 
      />
    <div class="search-bar-options" v-if="isVisible">
      <div v-for="category in filteredUser" :key="category.id" @click="selectCategory(category)">
        <p class="category">{{ category.name }}</p>
      </div>
      <div v-if="filteredUser.length === 0">
        <p>No results found!</p>
      </div>
    </div>
  </div>
</div>