1.0.0-alpha.1 • Published 4 years ago

@francoischalifour/autocomplete.js v1.0.0-alpha.1

Weekly downloads
24
License
MIT
Repository
github
Last release
4 years ago

Autocomplete.js

Autocomplete.js is a JavaScript library that creates a fast and fully-featured auto-completion experience.


Version jsDelivr Hits MIT License

Features

  • Displays suggestions as you type
  • Shows top suggestion as a completion
  • Supports custom templates for UI flexibility
  • Works well with RTL languages
  • Triggers custom hooks to plug your logic
  • Plugs easily to Algolia's realtime search engine

Usage

Try it out live

HTML
<body>
  <div id="autocomplete"></div>
</body>
JavaScript
const items = [
  { value: 'Apple', count: 120 },
  { value: 'Banana', count: 100 },
  { value: 'Cherry', count: 50 },
  { value: 'Orange', count: 150 },
];

autocomplete({
  container: '#autocomplete',
  getSources() {
    return [
      {
        getSuggestions({ query }) {
          return items.filter(item =>
            item.value.toLocaleLowerCase().includes(query.toLocaleLowerCase())
          );
        },
        getInputValue({ suggestion }) {
          return suggestion.value;
        },
        templates: {
          suggestion({ suggestion }) {
            return `<div>${suggestion.value} (${suggestion.count})</div>`;
          },
        },
      },
    ];
  },
});

You can learn more about the options and the top-level API.

Installation

Autocomplete.js is available on the npm registry.

yarn add @francoischalifour/autocomplete.js
# or
npm install @francoischalifour/autocomplete.js

If you do not wish to use a package manager, you can use standalone endpoints:

<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/autocomplete.js/1"></script>

<!-- unpkg -->
<script src="https://unpkg.com/autocomplete.js/1"></script>

API

Options

container

string | HTMLElement | required

The container for the autocomplete search box.

getSources

(options: { query: string }) => AutocompleteSource[] | Promise<AutocompleteSource[]>

Called to fetch the sources.

dropdownContainer

string | HTMLElement | defaults to document.body

The container for the autocomplete dropdown.

dropdownPosition

'left' | 'right' | defaults to 'left'

The dropdown position related to the container.

placeholder

string | defaults to ""

The text that appears in the search box input when there is no query.

It is fowarded to the input's placeholder.

showCompletion

boolean | defaults to false

Whether to show the highlighted suggestion as completion in the input.

`showCompletion` preview

minLength

number | defaults to 1

The minimum number of characters long the autocomplete opens.

autofocus

boolean | defaults to false

Whether to focus the search box when the page is loaded.

keyboardShortcuts

string[]

The keyboard shortcuts keys to focus the input.

defaultHighlightedIndex

number | defaults to 0 (the first item)

The default item index to pre-select.

stallThreshold

number | defaults to 300

The number of milliseconds that must elapse before the autocomplete experience is stalled. The timeout is set from the moment getSources is called.

When the experience is stalled:

  • The CSS class algolia-autocomplete--stalled is added to the autocomplete container
  • The isStalled boolean is true in the state

initialState

State

The initial state to apply when the page is loaded.

templates

GlobalTemplates

Refer to the "Global Templates" section.

transformResultsRender

(results: JSX.Element[]) => JSX.Element | JSX.Element[]

Called before rendering the results.

Useful to wrap results in containers to organize the display.

autocomplete({
  // ...
  transformResultsRender(results) {
    const [recentSearches, querySuggestions, products] = results;

    return (
      <div style={{ display: 'flex' }}>
        <div style={{ flex: 1 }}>
          {recentSearches}
          {querySuggestions}
        </div>

        <div style={{ flex: 2 }}>{products}</div>
      </div>
    );
  },
});

environment

typeof window | defaults to window

The environment from where your JavaScript is running.

Useful if you're using Autocomplete.js in a different context than window.

onFocus

(options: { state: AutocompleteState, ...setters }) => void

Called when the input is focused.

This function is also called when the input is clicked while already having the focus and the dropdown is closed.

onError

(options: { state: AutocompleteState, ...setters }) => void | defaults to ({ state }) => throw state.error

Called when an error is thrown while getting the suggestions.

When an error is caught:

  • The error is thrown (default onError implementation)
  • The CSS class algolia-autocomplete--errored is added to the autocomplete container
  • The error is available in the state

onClick

(event: MouseEvent, options: { state: AutocompleteState, ...setters, suggestion: any, suggestionValue: string }) => void

Called when a click event is fired on an item.

This function is useful to alter the behavior when a special key is held (e.g. keeping the dropdown open when the meta key is used).

onKeyDown

(event: KeyboardEvent, options: { state: AutocompleteState, ...setters, suggestion?: any, suggestionValue?: string }) => void

Called when a keydown event is fired.

This function is useful to alter the behavior when a special key is held.

autocomplete({
  // ...
  onKeyDown(event, { suggestion, state, setIsOpen }) {
    if (!suggestion || !suggestion.url) {
      return;
    }

    if (event.key === 'Enter') {
      if (event.metaKey || event.ctrlKey) {
        setIsOpen(true);

        const windowReference = window.open(suggestion.url, '_blank');
        windowReference.focus();
      } else if (event.shiftKey) {
        window.open(suggestion.url, '_blank');
      } else if (event.altKey) {
        // Keep native browser behavior
      } else {
        window.location.assign(suggestion.url);
      }
    }
  },
});

onInput

(options: { query: string, state: AutocompleteState, ...setters }) => void | Promise <void | { state: AutocompleteState }>

Called when the input changes.

This turns experience is "controlled" mode. You'll be in charge of updating the state with the top-level API.

Sources

An Autocomplete source refers to an object with the following properties:

getInputValue

(options: { suggestion: Suggestion, state: AutocompleteState }) => string

Called to get the value of the suggestion. The value is used to fill the search box.

If you do not wish to update the input value when an item is selected, you can return state.query.

const items = [{ value: 'Apple' }, { value: 'Banana' }];

const source = {
  getInputValue: ({ suggestion }) => suggestion.value,
  // ...
};

getSuggestions

(options: { query: string, state: AutocompleteState, ...setters }) => Suggestion[] | Promise<Suggestion[]> | required

Called when the input changes. You can use this function to filter/search the items based on the query.

const items = [{ value: 'Apple' }, { value: 'Banana' }];

const source = {
  getSuggestions({ query }) {
    return items.filter(item => item.value.includes(query));
  },
  // ...
};

templates

required

Templates to use for the source. A template supports strings and JSX elements.

header

(options: { state: AutocompleteState, ...setters }) => string | JSX.Element

The template to display before the suggestions.

suggestion

(options: { suggestion: Suggestion, state: AutocompleteState, ...setters }) => string | JSX.Element

The template for each suggestion.

footer

(options: { state: AutocompleteState, ...setters }) => string | JSX.Element

The template to display after the suggestions.

empty

(options: { state: AutocompleteState, ...setters }) => string | JSX.Element

The template to display when there are no suggestions.

Using strings

const items = [{ value: 'Apple' }, { value: 'Banana' }];

const source = {
  templates: {
    header() {
      return '<h2>Fruits</h2>';
    },
    suggestion({ suggestion }) {
      return suggestion.value;
    },
    footer() {
      return '<a href="/fruits">See more</a>';
    },
  },
  // ...
};

Using JSX elements

const items = [{ value: 'Apple' }, { value: 'Banana' }];

const source = {
  templates: {
    header() {
      return <h2>Fruits</h2>;
    },
    suggestion({ suggestion }) {
      return suggestion.value;
    },
    footer() {
      return <a href="/fruits">See more</a>;
    },
  },
  // ...
};

onSelect

(options: { state: AutocompleteState, ...setters }) => void

Called when an item is selected.

State

The Autocomplete.js state drives the behavior of the experience.

Getters

The state can be initially set with initialState and it's is passed to all templates.

query

string | defaults to ''

The query.

results

Result[] | defaults to []

The results of all the sources.

interface Result {
  source: AutocompleteSource;
  suggestions: any[];
}
isOpen

boolean | defaults to false

Whether the dropdown is open.

isLoading

boolean | defaults to false

Whether the experience is loading.

isStalled

boolean | defaults to false

Whether the experience is stalled.

error

null | Error | defaults to null

The error that happened, null if none.

context

object | defaults to {}

The autocomplete context to store data in. It's useful to use custom data in templates.

Setters

Each state has a setter that can be used in the lifecycle of Autocomplete.js.

setQuery

(value: string | ((prevState: string) => string)) => void

Sets the query value in the state.

setResults

(value: Result[] | ((prevState: Result[]) => Result[])) => void

Sets the results value in the state.

setIsOpen

(value: boolean | ((prevState: boolean) => boolean)) => void

Sets the isOpen value in the state.

setIsLoading

(value: boolean | ((prevState: boolean) => boolean)) => void

Sets the isLoading value in the state.

setIsStalled

(value: boolean | ((prevState: boolean) => boolean)) => void

Sets the isStalled value in the state.

setError

(value: Error | null | ((prevState: Error | null) => Error | null)) => void

Sets the error value in the state.

setContext

(value: object | ((prevState: object) => object)) => void

Sets the context value in the state.

Storing nbHits from the Algolia response

autocomplete({
  // ...
  getSources({ query, setContext }) {
    return getAlgoliaResults({
      searchClient,
      query,
      searchParameters: [
        {
          indexName: 'instant_search',
          params: {
            attributesToSnippet: ['description'],
          },
        },
      ],
    }).then(results => {
      const productsResults = results[0];

      setContext({
        nbProducts: productsResults.nbHits,
      });

      return [
        {
          // ...
          templates: {
            header({ state }) {
              return `<h2>Products (${state.context.nbProducts})</h2>`;
            },
          },
        },
      ];
    });
  },
});

Global templates

In addition to the source templates, Autocomplete.js supports some global templates.

header

(options: { state: AutocompleteState, ...setters }) => string | JSX.Element

The template to display before all sources.

footer

(options: { state: AutocompleteState, ...setters }) => string | JSX.Element

The template to display after all sources.

Top-level API

autocomplete

autocomplete is the default export from the autocomplete.js package. It is the main function that starts the autocomplete experience and accepts options.

The autocomplete function returns an API that allows you to turn Autocomplete.js in a "controlled" mode. It returns all the setters so that you update the state of the experience.

// Instantiate Autocomplete.js
const autocompleteSearch = autocomplete({
  // options
});

// Retrieve the state of your app that you want to forward to Autocomplete.js
const app = getAppState();

// Update the state of Autocomplete.js based on your app state
autocompleteSearch.setQuery(app.query);
autocompleteSearch.setResults(
  app.indices.map(index => {
    return {
      source: getSource({ index }),
      suggestions: index.hits,
    };
  })
);
autocompleteSearch.setIsOpen(app.isOpen);
autocompleteSearch.setIsLoading(app.isLoading);
autocompleteSearch.setIsStalled(app.isStalled);
autocompleteSearch.setError(app.error);
autocompleteSearch.setContext(app.context);

Algolia presets

Autocomplete.js comes with presets to facilitate the integration with Algolia.

getAlgoliaHits

(options: { searchClient: SearchClient, query: string, searchParameters: SearchParameters[] }) => Promise<Response['hits']>

Function that retrieves and merges Algolia hits from multiple indices.

This function comes with default Algolia search parameters:

import autocomplete, { getAlgoliaHits } from 'autocomplete.js';
import algoliasearch from 'algoliasearch';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

autocomplete({
  // ...
  getSources({ query }) {
    return [
      {
        // ...
        getSuggestions({ query }) {
          return getAlgoliaHits({
            searchClient,
            query,
            searchParameters: [
              {
                indexName: 'instant_search',
                params: {
                  hitsPerPage: 3,
                },
              },
            ],
          });
        },
      },
    ];
  },
});

getAlgoliaResults

(options: { searchClient: SearchClient, query: string, searchParameters: SearchParameters[] }) => Promise<MultiResponse['results']>

Function that retrieves Algolia results from multiple indices.

This function comes with default Algolia search parameters:

import autocomplete, { getAlgoliaResults } from 'autocomplete.js';
import algoliasearch from 'algoliasearch';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

autocomplete({
  // ...
  getSources({ query }) {
    return [
      {
        // ...
        getSuggestions({ query }) {
          return getAlgoliaResults({
            searchClient,
            query,
            searchParameters: [
              {
                indexName: 'instant_search',
                params: {
                  hitsPerPage: 3,
                },
              },
            ],
          }).then(results => {
            const firstResult = results[0];

            return firstResult.hits;
          });
        },
      },
    ];
  },
});

highlightAlgoliaHit

Highlights and escapes the value of a record.

import autocomplete, { highlightAlgoliaHit } from 'autocomplete.js';

autocomplete({
  // ...
  templates: {
    suggestion({ suggestion }) {
      return highlightAlgoliaHit({
        hit: suggestion,
        attribute: 'name',
      });
    },
  },
});

reverseHighlightAlgoliaHit

This function reverse-highlights and escapes the value of a record.

It's useful when following the pattern of Query Suggestions to highlight the difference between what the user types and the suggestion shown.

import autocomplete, { reverseHighlightAlgoliaHit } from 'autocomplete.js';

autocomplete({
  // ...
  templates: {
    suggestion({ suggestion }) {
      return reverseHighlightAlgoliaHit({
        hit: suggestion,
        attribute: 'query',
      });
    },
  },
});

snippetAlgoliaHit

Highlights and escapes the snippet value of a record.

import autocomplete, { snippetAlgoliaHit } from 'autocomplete.js';

autocomplete({
  // ...
  templates: {
    suggestion({ suggestion }) {
      return snippetAlgoliaHit({
        hit: suggestion,
        attribute: 'name',
      });
    },
  },
});

Design

Search box

<div
  class="algolia-autocomplete"
  role="combobox"
  aria-haspopup="listbox"
  aria-labelledby="autocomplete-0-label"
>
  <form role="search" novalidate="" class="algolia-autocomplete-form">
    <label
      for="autocomplete-0-input"
      class="algolia-autocomplete-magnifierLabel"
    >
      <svg>
        ...
      </svg>
    </label>

    <div class="algolia-autocomplete-loadingIndicator">
      <svg>
        ...
      </svg>
    </div>

    <div class="algolia-autocomplete-searchbox">
      <input
        id="autocomplete-0-input"
        class="algolia-autocomplete-input"
        aria-autocomplete="list"
        aria-labelledby="autocomplete-0-label"
        autocomplete="off"
        placeholder="Search…"
        type="search"
        autocorrect="off"
        autocapitalize="off"
        spellcheck="false"
        maxlength="512"
      />
    </div>

    <button
      type="reset"
      title="Clear the query"
      class="algolia-autocomplete-reset"
      hidden="true"
    >
      <svg>
        ...
      </svg>
    </button>
  </form>
</div>

Dropdown

<div class="algolia-autocomplete-dropdown">
  <div class="algolia-autocomplete-dropdown-container">
    <header class="algolia-autocomplete-header">
      Global header
    </header>

    <section class="algolia-autocomplete-suggestions">
      <header class="algolia-autocomplete-suggestions-header">
        <h2>Fruits</h2>
      </header>

      <ul
        id="autocomplete-0-menu"
        role="listbox"
        aria-labelledby="autocomplete-0-label"
      >
        <li
          class="algolia-autocomplete-suggestions-item"
          id="autocomplete-0-item-0"
          role="option"
          tabindex="0"
        >
          Apple
        </li>
        <li
          class="algolia-autocomplete-suggestions-item"
          id="autocomplete-0-item-1"
          role="option"
          tabindex="0"
        >
          Banana
        </li>
      </ul>

      <footer class="algolia-autocomplete-suggestions-footer">
        Showing 2 out of 10 fruits
      </footer>
    </section>

    <footer class="algolia-autocomplete-footer">
      Global footer
    </footer>
  </div>
</div>

Examples

Browser support

Contributing

Please refer to the contributing guide.

License

Autocomplete.js is MIT licensed.