DEV Community

artydev
artydev

Posted on

Simple Autocomplete

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

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay