# Autocomplete

The Autocomplete component filters dropdown options based on what the user enters into the input.

## Demo

{% demo 'Autocomplete', 'edsAutocompleteDemo', demos.edsAutocompleteDemo, component.brand %}

## When to use this component

Use the Autocomplete component when users need to search and select from a large list of options.

If you need users to select specific options, for example institution names that must be in a particular format, the Autocomplete component may help to reduce errors.

Autocomplete can also help in cases where the list is too large for a standard select dropdown.

## When not to use this component

Do not use the Autocomplete component when:

- the list has fewer than 10 options - use a select dropdown or radio buttons instead
- users need to see all options before selecting - use a select dropdown
- the search requires complex filtering - consider a dedicated search page

## How it works

You must use the Autocomplete component with the Form component.

The Autocomplete component enhances a standard text input with a dropdown of matching results.

As users enter what they are looking for in the input field, the component:

1. Waits for a minimum number of characters (default: 2).
2. Debounces input to avoid excessive API calls (default: 300ms).
3. Fetches matching results from your API endpoint.
4. Displays results in a dropdown below the input.
5. Allows selection with mouse or keyboard.
6. Dispatches a custom event with the selected item's data.

### Installation

To install the components, [go to the get started guide for developers](https://github.com/springernature/elements).

### Styles

Import the styles of the Autocomplete component into your `scss` file:

```scss
// enhanced.scss
@import '../path/to/@springernature/elements/components/eds-c-autocomplete';
```

### Configuration

To view all the properties of this component, [go to the schema](https://github.com/springernature/elements/blob/main/components/eds-c-autocomplete/data/eds-c-autocomplete.schema.json).

#### Template

The component template accepts the following options (with Handlebars data):

| Option | Type | Description | Required |
|--------|------|-------------|----------|
| `input` | Object | Input field configuration (see [eds-c-input schema](https://github.com/springernature/elements/blob/main/components/eds-c-input/data/eds-c-input.schema.json)). | Yes |
| `dataAttributes` | Object | Custom data attributes for the wrapper element. | No |

#### JavaScript configuration

When initializing the component with JavaScript, you can pass these options:

| Option | Type | Description | Default |
|--------|------|-------------|---------|
| `apiUrl` | String | URL endpoint for fetching autocomplete results. | `null` |
| `apiQueryParam` | String | Query parameter name for the search term. | `'q'` |
| `apiMethod` | String | HTTP method for API requests (`'GET'` or `'POST'`). | `'GET'` |
| `apiCredentials` | String | Credentials mode: `'same-origin'`, `'include'`, or `'omit'`. | `'same-origin'` |
| `apiBodyFormat` | String | POST body format: `'json'` or `'urlencoded'`. | `'json'` |
| `apiHeaders` | Object | Custom headers (e.g., CSRF tokens, authentication). | `{}` |
| `transformResponse` | Function | Transform API response to expected format. | `null` |
| `minChars` | Number | Minimum characters before showing results. | `2` |
| `debounceDelay` | Number | Delay in milliseconds before triggering search. | `300` |
| `maxResults` | Number | Maximum number of results to display in the dropdown. | `20` |
| `maxQueryLength` | Number | Maximum query string length accepted (reads from input's `maxlength` attribute first, then this option, then defaults to 100). | `100` |
| `noResultMsg` | String | Message displayed when search returns no results. | `'No results found for'` |
| `loadingMsg` | String | Message displayed while fetching results from API. | `'Loading...'` |
| `errorMsg` | String | Message displayed when API request fails. | `'Unable to load results. Please try again.'` |

#### API response format

The component expects an array of objects.

The minimum required data are `id` and `name`. Additional properties like `city` and `country` are optional.

```javascript
[
    {
        "id": "99",             // Unique identifier
        "bpid": "12345",           // Business partner ID (used as form value)
        "name": "University Name", // Display text
        "city": "London",          // City name
        "country": "GB"            // Country code
    }
]
```

If your API returns a different structure, use the `transformResponse` option to convert it:

```javascript
// API returns: { results: [...], metadata: {...} }
new EdsCAutocomplete(wrapper, {
    apiUrl: '/api/search',
    transformResponse: (data) => {
        // Transform API response to expected format
        return data.results.map(item => ({
            id: item.id,
            bpid: item.id,
            name: item.institution_name,
            city: item.location.city,
            country: item.location.country_code
        }));
    }
});
```

### States

The component displays different states:

- Loading: Shows the text label "Loading..." while fetching results
- No results: Shows "No results found" when query returns empty
- Error: Shows "Unable to load results. Please try again." on API failure

### Keyboard support

| Key | Action |
|-----|--------|
| `ArrowDown` | Move focus to next result. |
| `ArrowUp` | Move focus to previous result. |
| `Enter` | Select the focused result. |
| `Escape` | Close the dropdown. |
| `Tab` | Close the dropdown and move to next element. |

### Accessibility

The component includes a persistent ARIA live region (`aria-live="assertive"`) that announces state changes to screen reader users:

- announces "Loading..." when fetching results
- announces "No results found for [query]" when the search returns no matches
- announces "Unable to load results. Please try again." on API errors

The live region is visually hidden but remains in the DOM, ensuring reliable announcements without interfering with the visual interface.

### Progressive enhancement

Without JavaScript, the component functions as a standard form input that submits to your server. With JavaScript enabled, it provides enhanced autocomplete functionality while maintaining the same form structure.

### Example: Form integration with hidden input

To capture the selected item's ID for form submission, add a `data-autocomplete-hidden` attribute to the hidden input field:

```html
{% raw %}
<form class="eds-c-form" action="/your-search-endpoint" method="get">
    <div data-eds-c-autocomplete>
        {{#with autocomplete}}
            {{> eds-c-autocomplete}}
        {{/with}}
    </div>
    <input type="hidden" name="selected-item-id" value="" data-eds-c-autocomplete-hidden>
    <div class="u-mt-24">
        {{#with button}}
            {{> eds-c-button}}
        {{/with}}
    </div>
</form>
{% endraw %}
```

### Javascript

```javascript
const autocompleteWrappers = document.querySelectorAll('[data-eds-c-autocomplete]');
const autocompleteInstances = [];
for (const wrapper of autocompleteWrappers) {
	const autocomplete = new EdsCAutocomplete(wrapper, { apiUrl: '/api/autocomplete' });
	// Listen for selection event
	wrapper.addEventListener('autocomplete:select', (e) => {
		console.log('autocomplete selected', e.detail);
		// Find hidden input in parent form (scoped to form)
		const form = wrapper.closest('form');
		if (form) {
			const hiddenInput = form.querySelector('[data-eds-c-autocomplete-hidden]');
			if (hiddenInput) {
				hiddenInput.value = e.detail.value;
			}
		}
	});
	autocompleteInstances.push(autocomplete);
}
```

### Events

The component dispatches a custom event when a user selects an item:

```javascript
wrapper.addEventListener('autocomplete:select', (event) => {
    console.log('Selected institution:', event.detail);
});
```

### Security (CSRF) for logged in usage

If sensitive data exposure could be a concern, protect the endpoint with a CSRF (Cross‑Site Request Forgery) token. A synchronizer CSRF token is a secret value the server puts into pages (meta tag or hidden field) and the client must send back with requests so the server can verify they come from your site.

This helps to prevent malicious third‑party pages from making authenticated requests using the user’s cookies. If you do not implement this, attackers could cause unwanted authenticated requests. Even if search is read‑only, follow your app security policy. Include a token in the page and send it in request headers for POST/credentialed fetches.

#### Client side

Once the user is logged in, add a meta tag in your page template:

```html
<meta name="csrf-token" content="{{csrfToken}}">
````
The token must be per session per user.

Then, in your JavaScript, read the token and include it in requests:

```javascript
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';

const ac = new EdsCAutocomplete(wrapper, {
  apiUrl: '/api/autocomplete',
  apiMethod: 'POST',
  apiCredentials: 'same-origin',
  apiHeaders: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': csrfToken
  }
});
```

#### Server side

Node.js/Express example:

```javascript
router.post('/api/autocomplete', (req, res) => {
  const tokenFromClient = req.get('X-CSRF-Token') || req.body._csrf;
  const tokenInSession = req.session && req.session.csrfToken;

  if (!tokenFromClient || tokenFromClient !== tokenInSession) {
    return res.status(403).json({ error: 'Invalid CSRF token' });
  }

  // handle search...
});
```
