Skip to content

Haroenv/vue-autosuggest

 
 

Repository files navigation

vue-autosuggest

🔍 Autosuggest component built for Vue.


Build Status Code Coverage version downloads MIT License

All Contributors PRs Welcome Code of Conduct

Watch on GitHub Star on GitHub Tweet

Table of Contents

Examples

Features

  • WAI-ARIA complete autosuggest component built with the power of Vue.
  • Full control over rendering with built in defaults or custom components for rendering.
  • Easily integrate AJAX data fetching for list presentation.
  • Supports multiple sections.
  • No opinions on CSS, full control over styling.
  • Rigorously tested.

Installation

This module is distributed via npm which is bundled with node and should be installed as one of your project's dependencies:

npm install --save vue-autosuggest

or

yarn add vue-autosuggest

Usage

Load VueAutosuggest into your vue app globally.

import VueAutosuggest from "vue-autosuggest";
Vue.use(VueAutosuggest);

or locally inside a component:

import { VueAutosuggest } from 'vue-autosuggest';
export default {
  ...
  components: {
      VueAutosuggest
  }
  ...
};

Place the component into your app!

<vue-autosuggest
    :suggestions="[{data:['Frodo', 'Samwise', 'Gandalf', 'Galadriel', 'Faramir', 'Éowyn']}]"
    :on-selected="selectHandler"
    :input-props="{id:'autosuggest__input', onInputChange: onInputChange, placeholder:'Do you feel lucky, punk?'}"
    @click="clickHandler"
>  
  <template slot-scope="{suggestion}">
    <span class="my-suggestion-item">{{suggestion.item}}</span>
  </template>
</vue-autosuggest>

Advanced usage:

Click to expand

<template>
<div>
    <h1>Vue-autosuggest 🔮</h1>
    <div style="padding-top:10px; margin-bottom: 10px;"><span v-if="selected">You have selected '{{JSON.stringify(selected,null,2)}}'</span></div>
        <vue-autosuggest
            :suggestions="filteredOptions"
            @focus="focusMe"
            @click="clickHandler"
            :on-selected="onSelected"
            :render-suggestion="renderSuggestion"
            :get-suggestion-value="getSuggestionValue"
            :input-props="{id:'autosuggest__input', onInputChange: this.onInputChange, placeholder:'Do you feel lucky, punk?'}"/>
</div>
</template>

<script>
import { VueAutosuggest } from "vue-autosuggest";

export default {
  components: {
    VueAutosuggest
  },
  data() {
    return {
      selected: "",
      filteredOptions: [],
      suggestions: [
        {
          data: [
            { id: 1, name: "Frodo", avatar: "./frodo.jpg" },
            { id: 2, name: "Samwise", avatar: "./samwise.jpg" },
            { id: 3, name: "Gandalf", avatar: "./gandalf.png" },
            { id: 4, name: "Aragorn", avatar: "./aragorn.jpg" }
          ]
        }
      ]
    };
  },
  methods: {
    onInputChange(text, oldText) {
      if (text === null) {
        /* Maybe the text is null but you wanna do
        *  something else, but don't filter by null.
        */
        return;
      }

      // Full customizability over filtering
      const filteredData = this.suggestions[0].data.filter(option => {
        return option.name.toLowerCase().indexOf(text.toLowerCase()) > -1;
      });

      // Store data in one property, and filtered in another
      this.filteredOptions = [{ data: filteredData }];
    },
    clickHandler(item) {
      // items are selected by default on click, but you can add some more behavior here!
    },
    onSelected(item) {
      this.selected = item;
    },
    /**
     * renderSuggestion will override the default suggestion template slot.
     */
    renderSuggestion(suggestion) {
      /* You will need babel-plugin-transform-vue-jsx for this kind of syntax for
       * rendering. If you don't use babel or the jsx transform, then you can create 
       * the you can create the virtual node yourself using this.$createElement.
       */
      const character = suggestion.item;
      return (
        <div
          style={{
            display: "flex",
            alignItems: "center"
          }}
        >
          <img
            style={{
              width: "25px",
              height: "25px",
              borderRadius: "15px",
              marginRight: "10px"
            }}
            src={character.avatar}
          />{" "}
          <span style={{ color: "navyblue" }}>{character.name}</span>
        </div>
      );
    },
    /**
     * This is what the <input/> value is set to when you are selecting a suggestion.
     */
    getSuggestionValue(suggestion) {
      return suggestion.item.name;
    },
    focusMe(e) {
      console.log(e)
    }
  }
};
</script>

For more advanced usage, check out the examples below, and explore the properties you can use.

header/footer

Slots for injecting content above all the results inside the results container.

<vue-autosuggest ...>
  <template slot="header">
    <h1>header content goes here</h1>
  </template>
  <template slot="footer">
    <h1>footer content goes here</h1>
  </template>
</vue-autosuggest>

suggestion item (i.e. default slot)

Used to style each suggestion inside the <li> tag. Using scoped slots you have access to the suggestion item inside the v-for suggestions loop. This gives you the power of Vue templating, since vue-autosuggest does not have an opinion about how you render the items in your list.

<vue-autosuggest>
  <template slot-scope="{suggestion}">
    <!-- suggestion.name corresponds to which section the item is in -->
    <div v-if="suggestion.name === 'blog'">
      <!-- suggestion.item corresponds to the suggestion object -->
      <a target="_blank" :href="suggestion.item.url">{{suggestion.item.value}}</a>
    </div>
    <div v-else>{{suggestion.item}}</div>
  </template>
</vue-autosuggest>

This slot will be overridden when the render-suggestion prop is used.

Prop Type Required Description
suggestions Array Suggestions to be rendered.
input-props Object Add props to the <input>.
section-configs Object Define multiple sections <input>.
render-suggestion Function Tell vue-autosuggest how to render inside the <li> tag. Overrides what is inside the default suggestion template slot.
get-suggestion-value Function Tells vue-autosuggest what to put in the <input/> value

inputProps

Prop Type Required Description
id String id attribute on <input>.
on-input-change Function Triggers everytime the <input> changes. This is triggered via a Vue watcher, so you have both current value, and previous value access e.g. onInputChange(text, oldText)
onClick Function Deprecated Triggers everytime the <input> is clicked. You can now use @click which will map to the underlying <input />
onBlur Function Deprecated HTML onblur event on <input> same as Vue @blur event binding. You can now use @blur which will map to the underlying <input />
onFocus Function Deprecated HTML onfocus event on <input> same as Vue @focus event binding You can now use @focus which will map to the underlying <input />
initial-value String Set some initial value for the <input>.
Any DOM Props * You can add any props to <input> as the component will v-bind inputProps. Similar to rest spread in JSX. See more details here: https://vuejs.org/v2/api/#v-bind. The name attribute is set to "q" by default.

sectionConfigs

Multiple sections can be defined in the sectionConfigs prop which defines the control behavior for each section.

Prop Type Required Description
on-selected Function Determine behavior for what should happen when a suggestion is selected. e.g. Submit a form, open a link, update a vue model, tweet at Ken Wheeler etc.
type String Vue component name for specifying which type to implement using Vue's <component :is="componentName"></component> functionality. See DefaultSection.vue for scaffolding a new type. You must declare your component in the scope of the app using Vue.component(). You can extend DefaultSection using extends. See UrlSection for an example.
limit Number Limit each section by some value. Default: Infinity

Below we have defined a default section and a blog section. The blog section has a component type of url-section which corresponds to which component the Autosuggest loads. When type is not defined, Vue-autosuggest will use a built in DefaultSection.vue component.

sectionConfigs: {
    'default': {
        limit: 6,
        onSelected: function(item, originalInput) {
            console.log(item, originalInput, `Selected "${item.item}"`);
        }
    },
    'blog': {
        limit: 3,
        type: "url-section",
        onSelected: function() {
            console.log("url: " + item.item.url);
        }
    }
}

renderSuggestion

This function can be used to tell vue-autosuggest how to render the html inside the <li> tag when you do not want to use the default template slot for suggestions but would rather have the power of javascript / jsx.

In its most basic form it just returns an object property:

renderSuggestion(suggestion) {
    return suggestion.name;
},

But usually it returns a JSX fragment, which is transformed into a virtual node description with babel-plugin-transform-vue-jsx:

renderSuggestion(suggestion) {
    return <div style={{ color: "red" }}>{suggestion.name}</div>;
},

If you're not using babel-plugin-transform-vue-jsx, you can create the virtual node description yourself:

renderSuggestion(suggestion) {
    return this.$createElement('div', { 'style': { color: 'red'} }, suggestion.name);
},

getSuggestionValue

This function will tell vue-autosuggest what to put in the <input/> as the value.

getSuggestionValue(suggestion) {
    return suggestion.name;
},

FAQ

How do I update the input programatically?

  • You can assign a ref to the component <vue-autosuggest ref="myRefName" ... /> and then access the input value through this.$refs.myRefName.searchInput. This is useful mainly for clearing the input. ⚠️ Note, refs are more of an "escape hatch" as they call it, so it won't trigger the onInputChange method.

Inspiration

Contributors

Thanks goes to these people (emoji key):


Darren Jennings

💻 📖 🚇 ⚠️ 🎨 💡

Evgeniy Kulish

💻 🎨 💡 ⚠️

This project follows the all-contributors specification. Contributions of any kind welcome!

LICENSE

MIT

Packages

No packages published

Languages

  • JavaScript 64.3%
  • Vue 31.4%
  • HTML 4.3%