Home > Software engineering >  Vuejs auto suggest input box leading to extra space in the html
Vuejs auto suggest input box leading to extra space in the html

Time:08-08

Auto suggest uses extra spaces in the html, because of the reason the next elements like H1, H2 and paragraph are going down when I start typing in the suggestion box

App.vue

<template>
  <div id="app">
    <Autocomplete
      :items="[
        'Apple',
        'Banana',
        'Orange',
        'Mango',
        'Pear',
        'Peach',
        'Grape',
        'Tangerine',
        'Pineapple',
      ]"
    />
    <p>p1</p>
    <h1>h1</h1>
    <h1>h2</h1>
  </div>
</template>

<script>
import Autocomplete from "./components/AutoComplete";
import AutoCompleteSearch from "./directives/auto-complete-search.js";

import axios from "axios";

export default {
  name: "App",
  directives: {
    AutoCompleteSearch,
  },
  components: {
    Autocomplete,
  },
  data() {
    return {
      data: [],
    };
  },
  methods: {
    async getUser() {
      await axios
        .get("https://jsonplaceholder.typicode.com/users")
        .then((res) => (this.data = res.data))
        .catch((err) => console.log(err));
    },
  },
  mounted() {
    this.data = [{ name: "Elie" }, { name: "John" }];
  },
};
</script>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

AutocompletSearch.js -- directive

import Vue from "vue";

/**
 * Add the list attribute to the bound input field.
 *
 * @param element
 * @param binding
 * @returns {HTMLElement}
 */
const addAttribute = (element, binding) => {
  const { id } = binding.value;
  element.setAttribute("list", id);

  return element;
};

/**
 * The search data for auto-completions
 *
 * @param binding
 * @returns {Object|Array}
 */
const searchData = (binding) => {
  const { data } = binding.value;
  return data;
};

/**
 * Construct the datalist HTMLElement
 *
 * @param element
 * @param binding
 * @returns {HTMLDataListElement}
 */
const constructDataList = (element, binding) => {
  const datalist = document.createElement("datalist");
  datalist.id = binding.value.id;

  const data = searchData(binding);
  data.forEach((item) => {
    const { autoCompleteValue } = binding.value;

    const option = document.createElement("option");
    option.value = item[autoCompleteValue];
    datalist.appendChild(option);
  });

  return datalist;
};

/**
 * @param el the element where to bind the directive
 * @param binding {id & autoCompleteValue & data}
 */
const init = (el, binding) => {
  const element = addAttribute(el, binding);

  const wrapper = element.parentNode;

  const datalist = constructDataList(element, binding);

  wrapper.appendChild(datalist);
};

export default Vue.directive("auto-complete-search", {
  update(el, binding, vnode) {
    const vm = vnode.context;
    const bindingData = binding.value;

    const componentName = vm.$options.name;
    const customCallbackName = bindingData.callbackName;
    console.log("component updated");

    // call the function to fetch auto complete dat
    vm.$options.methods.getUser();

    if (!vm.$options.methods.getUser && customCallbackName === undefined)
      return console.error(`the getUser() is neither defined or overriden in ${componentName} component, on
      the auto-complete-search directive`);

    if (binding.value.apply) {
      init(el, binding);
    }

    if (!binding.value.apply) {
      el.removeAttribute("list");
    }
  }
});

AutoComplete.vue

<template>
  <div >
    <input
      type="text"
      @input="onChange"
      v-model="search"
      @keydown.down="onArrowDown"
      @keydown.up="onArrowUp"
      @keydown.enter="onEnter"
    />
    <ul id="autocomplete-results" v-show="isOpen" >
      <li  v-if="isLoading">Loading results...</li>
      <li
        v-else
        v-for="(result, i) in results"
        :key="i"
        @click="setResult(result)"
        
        :
      >
        {{ result }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "AutoComplete",
  props: {
    items: {
      type: Array,
      required: false,
      default: () => [],
    },
    isAsync: {
      type: Boolean,
      required: false,
      default: false,
    },
  },
  data() {
    return {
      isOpen: false,
      results: [],
      search: "",
      isLoading: false,
      arrowCounter: -1,
    };
  },
  watch: {
    items: function (value, oldValue) {
      if (value.length !== oldValue.length) {
        this.results = value;
        this.isLoading = false;
      }
    },
  },
  mounted() {
    document.addEventListener("click", this.handleClickOutside);
  },
  destroyed() {
    document.removeEventListener("click", this.handleClickOutside);
  },
  methods: {
    setResult(result) {
      this.search = result;
      this.isOpen = false;
    },
    filterResults() {
      this.results = this.items.filter((item) => {
        return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
      });
    },
    onChange() {
      this.$emit("input", this.search);

      if (this.isAsync) {
        this.isLoading = true;
      } else {
        this.filterResults();
        this.isOpen = true;
      }
    },
    handleClickOutside(event) {
      if (!this.$el.contains(event.target)) {
        this.isOpen = false;
        this.arrowCounter = -1;
      }
    },
    onArrowDown() {
      if (this.arrowCounter < this.results.length) {
        this.arrowCounter = this.arrowCounter   1;
      }
    },
    onArrowUp() {
      if (this.arrowCounter > 0) {
        this.arrowCounter = this.arrowCounter - 1;
      }
    },
    onEnter() {
      this.search = this.results[this.arrowCounter];
      this.isOpen = false;
      this.arrowCounter = -1;
    },
  },
};
</script>

<style>
.autocomplete {
  position: relative;
}

.autocomplete-results {
  padding: 0;
  margin: 0;
  border: 1px solid #eeeeee;
  height: 120px;
  overflow: auto;
}

.autocomplete-result {
  list-style: none;
  text-align: left;
  padding: 4px 2px;
  cursor: pointer;
}

.autocomplete-result.is-active,
.autocomplete-result:hover {
  background-color: #4AAE9B;
  color: white;
}
</style>

Please help me out this, auto suggestion can show like popup-modal instead of using extra spaces in the html. like google chrome search bar

Here is my code link - https://codesandbox.io/s/vuejs-autocomplete-input-forked-fh31tr

CodePudding user response:

After checking out your codesandbox just add these 3 css properties to your autocomplete class:

    position: absolute;
    width: 100%;
    background: white;
  • Related