Simple Autocomplete

artydev - Oct 11 - - Dev Community

Here is a simple autocomplete using The French Government Design system.
You can see a demo here Demo

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Multiple Autocomplete Components with GUIDs</title>

  <!-- Include DSFR CSS using CDN (ensure you are using the latest version) -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr/dist/dsfr/dsfr.min.css">

</head>
<body>

<h4>
  Autocomplete DSFR
  </h4>
<!-- Multiple autocomplete-input components -->
<div class="container">
  <div class="autocomple-container">
      <autocomplete-input></autocomplete-input>
      <autocomplete-input></autocomplete-input> 
  </div>
   <ul class="fr-btns-group" style="max-width:415px;width:100%;margin:0 auto;">
      <li>
          <button  class="fr-btn">Calculer le prix en vigueur</button>
      </li>
  </ul>
</div>


<script>
  class AutocompleteInput extends HTMLElement {
    constructor() {
      super();

      // Attach Shadow DOM
      this.attachShadow({ mode: 'open' });

      // Generate a unique ID for this component instance
      this.uniqueId = this.generateGUID();

      // Template for the component
      const template = document.createElement('template');
      template.innerHTML = `
        <!-- Import DSFR stylesheet into the shadow DOM -->
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr/dist/dsfr/dsfr.min.css">

        <style>
          /* Custom scoped styles for the component */
          .fr-input-group {
            position: relative;
            margin-bottom:1rem !important;

            margin:0 auto;
          }

          ul {
            position: absolute;
            top: 100%;
            left: 0;
            right: 0;
            border: 1px solid #ccc;
            max-height: 150px;
            overflow-y: auto;
            background: white;
            list-style: none;
            padding: 0;
            margin: 0;
            z-index: 1000;
            display: none;
          }
          li {
            padding: 8px;
            cursor: pointer;
          }
          li:hover, .highlight {
            background-color: #ddd;
          }

          .container {
            width:100%;
            max-width:400px;
            margin:0 auto;
          }

        </style>

        <div class="container">
          <div class="fr-input-group">
            <!-- DSFR styled input -->
            <label for="${this.uniqueId}" class="fr-label"></label>
            <input id="${this.uniqueId}" class="fr-input" type="text" placeholder="Sélectionner"/>
            <ul></ul>
          </div>
        </div>
      `;

      // Attach the template to the shadow DOM
      this.shadowRoot.appendChild(template.content.cloneNode(true));

      // Internal component variables
      this.inputElement = this.shadowRoot.querySelector('input');
      this.listElement = this.shadowRoot.querySelector('ul');
      this.suggestions = [];
      this.filteredSuggestions = [];
      this.highlightedIndex = -1;

      // Event listeners
      this.inputElement.addEventListener('input', this.onInput.bind(this));
      this.inputElement.addEventListener('keydown', this.onKeyDown.bind(this));
      this.listElement.addEventListener('click', this.onSelect.bind(this));
    }

    // Method to generate a GUID for each instance
    generateGUID() {
      return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
      });
    }

    // Method to update suggestions from outside the component
    setSuggestions(suggestions) {
      this.suggestions = suggestions;
    }

    // Method called when user types in the input field
    onInput() {
      const inputValue = this.inputElement.value.toLowerCase().trim(); // Trim input value
      this.filteredSuggestions = this.suggestions.filter(suggestion =>
        suggestion.toLowerCase().includes(inputValue)
      );

      this.highlightedIndex = -1; // Reset highlighted index

      // Hide the list if input is empty or no matches
      if (inputValue === '' || this.filteredSuggestions.length === 0) {
        this.listElement.style.display = 'none';
      } else {
        this.updateList();
      }
    }

    // Method to update the suggestion list
    updateList() {
      this.listElement.innerHTML = '';
      if (this.filteredSuggestions.length > 0) {
        this.filteredSuggestions.forEach((suggestion, index) => {
          const listItem = document.createElement('li');
          listItem.textContent = suggestion;
          if (index === this.highlightedIndex) {
            listItem.classList.add('highlight');
          }
          this.listElement.appendChild(listItem);
        });
        this.listElement.style.display = 'block';
      } else {
        this.listElement.style.display = 'none'; // Ensure it's hidden when no matches
      }
    }

    // Handle keyboard navigation (arrow keys, Enter)
    onKeyDown(event) {
      const key = event.key;
      if (key === 'ArrowDown') {
        this.highlightedIndex = Math.min(this.highlightedIndex + 1, this.filteredSuggestions.length - 1);
        this.updateList();
      } else if (key === 'ArrowUp') {
        this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
        this.updateList();
      } else if (key === 'Enter') {
        if (this.highlightedIndex > -1) {
          this.inputElement.value = this.filteredSuggestions[this.highlightedIndex];
          this.listElement.style.display = 'none';
        }
      }
    }

    // Method called when the user selects a suggestion
    onSelect(event) {
      if (event.target.tagName === 'LI') {
        this.inputElement.value = event.target.textContent;
        this.listElement.style.display = 'none';
      }
    }
  }

  // Register the custom element
  customElements.define('autocomplete-input', AutocompleteInput);

  // Example of using the component with multiple instances
  const autocompleteInputs = document.querySelectorAll('autocomplete-input');

  // Set different suggestions for each instance
  autocompleteInputs[0].setSuggestions(['Table', 'Chaise', 'Armoire', 'Date']);
  autocompleteInputs[1].setSuggestions(['Paris', 'Rome', 'Naples']);
</script>

</body>
</html>

Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player