typeahead docs

2.11.0

Bolt Typeahead

Typeahead is an input field with dropdown-like listbox that displays suggested results that most closely match a user's given search term.

Bolt's Component Explorer is being upgraded. It'll return in a future release!

Installation

npm install @bolt/components-typeahead

Features

  • Progressively enhanced simple html <form> fallback (via Twig)
  • Server-side pre-rendered SVG icons (when using Twig)!
  • Uses the new withEvents base class to allow for much deeper JavaScript customization
  • Fuzzy logic search / fuzzy matching using fuse.io
  • Keyboard combo-support (command+shift+f)
  • Wired up to use CSS Modules (once they ship in a future Bolt release)
  • Fully customizable behavior to handle partial vs full result matches, etc
  • Supports rendering to the Shadow DOM and the Light DOM

What's Next? (Future Updates)

  • Fully support theming system colors
  • JSDoc support / further improve docs and demos
  • Broader testing coverage
  • Look into adding <slot> support
  • More customization for additional use cases?
  • Multi-section support

API

JavaScript Properties/Attributes

Name Type Description
items array An array of objects that populates the dropdown

JavaScript Event Hooks

Name Params Description
onInput event,
value
Called every time the input value changes
getSuggestions value Called by onSuggestionsFetchRequested when re-rendering suggestions. Handles highlighting keywords in the search results in a React-friendly way + handles limiting the total number of results displayed
onChange event,
newValue, method
Called when a suggestion is selected. Includes info on how the particular item was selected (click, keyboard, etc)
onSuggestionsFetchRequested value Called every very time you need to gather / update suggestions to display. See onSuggestionsFetchRequested for more info.
onSuggestionsClearRequested Called when clearing suggestions. See onSuggestionsClearRequested for more info.
onSelected event,
suggestion
Will be called every time suggestion is selected via mouse or keyboard. See onSuggestionSelected for more info.
onRenderInput value Fired when the input is being rendered

Additional references

Note: when assigning component props as HTML attributes on a web component, make sure to use kebab-case.

Prop Name Description Type Default Value Option(s)
Attributes

A Drupal-style attributes object with extra attributes to append to this component.

object
Max_results

The maximum number of typeahead results to display

number 10
Items

An array of objects that's used to populate the suggestion list that appears below the input as the users type. This array of objects can be asynchronously fetched and should contain a label, url, and optionally description.

array
Clear_input_text

Screenreader-specific text for the clear search button, intended to provide a longer, more descriptive explanation of the clear button's behavior.

string Clear search results
Submit_button_text

Screenreader-specific text for the submit button, intended to provide a longer, more descriptive explanation of the submit button's behavior.

string Submit search query
Input_label

Screenreader-specific label text associated with the search input.

string
Input_placeholder

The placeholder text to display inside the Typeahead search input.

string Enter your search query
Input_value

Initial value to pre-populate the input field

string
Input_name

Input element's name attribute used when pre-rendering the component

string
No_highlight

Disable text highlighting in matching search results (highlighting is enabled by default)

boolean false

Demo: Dyamically Fetch Data

In this example, we populate the Typeahead component with JSON data that's dynamically fetched from an external source via the getSuggestions hook.

Also, this demo caps the max # of search results to display at 5.


{% include "@bolt-components-form/form.twig" with {
  children: include("@bolt-components-typeahead/typeahead.twig", {
    attributes: {
      class: [
        "js-typeahead-hook--dynamically-fetch-data"
      ]
    },
    max_results: 5,
    input_name: "q"
  }),
  attributes: {
    action: "https://www.pega.com/search",
    target: "_blank",
    method: "GET"
  }
} %}

// NOTE: make sure you're running this code through a tool like Babel before shipping for cross browser compatibility!
const dynamicTypeaheadDemo = document.querySelector(
  '.js-typeahead-hook--dynamically-fetch-data',
);

if (dynamicTypeaheadDemo) {
  dynamicTypeaheadDemo.addEventListener('ready', function(e) {
    if (e.detail.name === 'bolt-typeahead') {
      // note: make sure to let Typeahead know when the data fetched is ready
      dynamicTypeaheadDemo.on('getSuggestions', async value => {
        return await new Promise(async resolve => {
          await fetch('/build/data/typeahead.data.json')
            .then(function(response) {
              return response.json();
            })
            .then(function(data) {
              return resolve(data);
            });
        });
      });

      dynamicTypeaheadDemo.on('onSelected', (element, event, suggestion) => {
        const exactMatch = element.items.filter(
          item => item.label === suggestion.suggestionValue,
        )[0];

        function navigateTo(url) {
          if (window.location !== window.parent.location) {
            const win = window.open(url, '_blank');
            win.focus();
          } else {
            window.location = url;
          }
        }

        if (exactMatch && exactMatch.url) {
          if (exactMatch.url) {
            navigateTo(exactMatch.url);
          } else {
            navigateTo(`https://www.pega.com/search?q=${itemSelected.label}`);
          }
        } else if (suggestion.suggestionValue !== '') {
          navigateTo(
            `https://www.pega.com/search?q=${suggestion.suggestionValue}`,
          );
        }
      });
    }
  });
}

In this example, we populate the Typeahead component with a small batch of results and customize the onSelected behavior to go directly to the result selected (vs the search results page) for exact matches.

Submitting the form with text that isn't a 1:1 match will fall back to the search results page (the behavior shown in the other demo).


{% include "@bolt-components-form/form.twig" with {
  children: include("@bolt-components-typeahead/typeahead.twig", {
    attributes: {
      class: [
        "js-typeahead-hook--exact-result"
      ]
    },
    max_results: 5,
  }),
  attributes: {
    action: "https://www.pega.com/search",
    target: "_blank",
    method: "GET"
  }
} %}

// NOTE: make sure you're running this code through a tool like Babel before shipping for cross browser compatibility!
const typeaheadDemo = document.querySelector(
  '.js-typeahead-hook--exact-result',
);

const typeaheadDemoItems = [
  {
    label: 'AI and improving the customer experience',
    description:
      '“Artificial intelligence” (AI) presents both distracting hype and powerful opportunities to drive customer engagement.',
    url: 'https://www.pega.com/ai-and-improving-customer-experience',
  },
  {
    label:
      'Gartner Magic Quadrant for Enterprise Low-Code Application Platforms 2019',
    description:
      'Pega was cited as a Visionary in Gartner’s new 2019 Magic Quadrant for Enterprise Low-Code Application Platforms.',
    url:
      'https://www.pega.com/insights/resources/gartner-magic-quadrant-enterprise-low-code-application-platforms-2019',
  },
];

if (typeaheadDemo) {
  typeaheadDemo.addEventListener('ready', function(e) {
    if (e.detail.name === 'bolt-typeahead') {
      typeaheadDemo.items = typeaheadDemoItems;

      typeaheadDemo.on('onSelected', (element, event, suggestion) => {
        const exactMatch = element.items.filter(
          item => item.label === suggestion.suggestionValue,
        )[0];

        function navigateTo(url) {
          if (window.location !== window.parent.location) {
            const win = window.open(url, '_blank');
            win.focus();
          } else {
            window.location = url;
          }
        }

        if (exactMatch && exactMatch.url) {
          if (exactMatch.url) {
            navigateTo(exactMatch.url);
          } else {
            navigateTo(`https://www.pega.com/search?q=${itemSelected.label}`);
          }
        } else if (suggestion.suggestionValue !== '') {
          navigateTo(
            `https://www.pega.com/search?q=${suggestion.suggestionValue}`,
          );
        }
      });
    }
  });
}

In this example, we populate the Typeahead component with a larger batch of results and customize the onSelected behavior to always go to the search results page when selecting an item.

Unlike the other demo, only perfect matches will allow you to submit (via the submit button) or select a result.

For example, manually selecting or entering Case Management in the input field will allow you to submit via hitting enter or clicking on the search icon. However, entering in Case Management2 instead would not submit.


{% include "@bolt-components-form/form.twig" with {
  children: include("@bolt-components-typeahead/typeahead.twig", {
    attributes: {
      class: [
        "js-typeahead-hook"
      ]
    },
    max_results: 5,
  }),
  attributes: {
    action: "https://www.pega.com/search",
    target: "_blank",
    method: "GET"
  }
} %}

// NOTE: make sure you're running this code through a tool like Babel before shipping for cross browser compatibility!
const typeahead = document.querySelector('.js-typeahead-hook');

const items = [
  {
    label: 'AI and improving the customer experience',
    description:
      '“Artificial intelligence” (AI) presents both distracting hype and powerful opportunities to drive customer engagement.',
    url: 'https://www.pega.com/ai-and-improving-customer-experience',
  },
  {
    label:
      'Gartner Magic Quadrant for Enterprise Low-Code Application Platforms 2019',
    description:
      'Pega was cited as a Visionary in Gartner’s new 2019 Magic Quadrant for Enterprise Low-Code Application Platforms.',
    url:
      'https://www.pega.com/insights/resources/gartner-magic-quadrant-enterprise-low-code-application-platforms-2019',
  },
  {
    label: 'Pega Data & Integrations',
    description:
      "Take full advantage of integration opportunities with Pega's open architecture, allowing you to build applications faster and meet the increasing demands of your business.",
    url: 'https://www.pega.com/products/pega-platform/data-integrations',
  },
  {
    label: 'Digital Customer Experiences',
    description:
      'Deliver engaging digital customer experiences anywhere, anytime, with unique designs for your business right out of the box.',
    url:
      'https://www.pega.com/products/pega-platform/digital-customer-experiences',
  },
  {
    label: 'DevOps and Testing',
    description:
      "Continuous integration and deployment. Continuous evolution. With one-click DevOps, you'll break barriers to delivery – and leapfrog competitors – by empowering business teams to…",
    url: 'https://www.pega.com/products/pega-platform/devops-testing',
  },
  {
    label: 'Pega Onboarding',
    description:
      'Intelligent work automation dramatically cuts time to revenue while ensuring compliance with global and local regulations, whether you are onboarding new clients, adding products…',
    url: 'https://www.pega.com/industries/financial-services/onboarding',
  },
  {
    label: 'Case Management',
    description:
      'Pega BPM and case management solutions allow you to build and manage enterprise-level strategic applications that can communicate with legacy systems.',
    url: 'https://www.pega.com/products/pega-platform/case-management',
  },
  {
    label: 'Pega Intelligent Virtual Assistant',
    description:
      "Across all channels, Pega's Intelligent Virtual Assistant engages users where they are and gives them experiences based on context, not just auto-responses.",
    url:
      'https://www.pega.com/products/pega-platform/intelligent-virtual-assistant',
  },
];

if (typeahead) {
  typeahead.addEventListener('ready', function(e) {
    if (e.detail.name === 'bolt-typeahead') {
      typeahead.items = items;
      typeahead.on('onSelected', (element, event, suggestion) => {
        const itemSelected = element.items.filter(
          item => item.label === suggestion.suggestionValue,
        )[0];

        if (itemSelected) {
          if (itemSelected.label) {
            if (window.location !== window.parent.location) {
              // const win = window.open(`${itemSelected.url}`, '_blank');
              const win = window.open(
                `https://www.pega.com/search?q=${itemSelected.label}`,
                '_blank',
              );
              win.focus();
            } else {
              window.location = `https://www.pega.com/search?q=${itemSelected.label}`;
            }
          }
        }
      });
    }
  });
}