https://github.com/yext/answers-search-ui
Answers Javascript API Library for building Search experiences.
https://github.com/yext/answers-search-ui
Last synced: 3 months ago
JSON representation
Answers Javascript API Library for building Search experiences.
- Host: GitHub
- URL: https://github.com/yext/answers-search-ui
- Owner: yext
- License: other
- Created: 2019-02-21T22:59:02.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2024-10-17T13:54:38.000Z (over 1 year ago)
- Last Synced: 2024-12-04T23:26:08.607Z (over 1 year ago)
- Language: JavaScript
- Homepage:
- Size: 86.1 MB
- Stars: 22
- Watchers: 11
- Forks: 7
- Open Issues: 21
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# Answers Search UI
1. [Install / Setup](#install-and-setup)
2. [ANSWERS.init Configuration Options](#answersinit-configuration-options)
- [Vertical Pages Configuration](#vertical-pages-configuration)
- [Search Configuration](#search-configuration)
- [Vertical No Results Configuration](#vertical-no-results-configuration)
- [onVerticalSearch Configuration](#onverticalsearch-configuration)
- [onUniversalSearch Configuration](#onuniversalsearch-configuration)
3. [Component Usage](#component-usage)
- [Base Component Configuration](#base-component-configuration)
- [Adding a Component](#adding-a-component-to-your-page)
- [Removing Components](#removing-components)
4. [Types of Built-in Components](#types-of-built-in-components)
- [SearchBar Component](#searchbar-component)
- [DirectAnswer Component](#direct-answer-component)
- [UniversalResults Component](#universal-results-component)
- [VerticalResults Component](#vertical-results-component)
- [VerticalResultsCount Component](#vertical-results-count-component)
- [Pagination Component](#pagination-component)
- [FilterBox Component](#filterbox-component)
- [Facets Component](#facets-component)
- [Transforming Facets](#transforming-facets)
- [FilterSearch Component](#filtersearch-component)
- [Filter Components](#filter-components)
- [Applied Filters Component](#applied-filters-component)
- [Navigation Component](#navigation-component)
- [QASubmission Component](#qa-submission-component)
- [SpellCheck Component](#spell-check-component)
- [LocationBias Component](#location-bias-component)
- [SortOptions Component](#sort-options-component)
- [Map Component](#map-component)
- [Supported Map Locales](#supported-map-locales)
- [Icon Component](#icon-component)
5. [Customizing Components](#customizing-components)
- [Using a Custom Renderer](#using-a-custom-renderer)
- [Custom Data Formatting](#custom-data-formatting)
- [Custom Data Transforms](#custom-data-transforms)
- [Using a Custom Template for a Component](#using-a-custom-template-for-a-component)
- [Creating Custom Components](#creating-custom-components)
6. [Extending the Built-in Renderer](#extending-the-built-in-renderer)
- [Custom Partials](#custom-partials)
- [Template Helpers](#template-helpers)
7. [Analytics](#analytics)
- [Custom Analytics Using JavaScript](#custom-analytics-using-javascript)
- [Custom Analytics Using Data Attributes](#custom-analytics-using-data-attributes)
- [Built-In Analytics Events For CTAs](#built-in-analytics-events-for-ctas)
- [Conversion Tracking](#conversion-tracking)
- [On-Search Analytics](#on-search-analytics)
8. [Rich Text Formatting](#rich-text-formatting)
9. [Processing Translations](#processing-translations)
10. [Performance Metrics](#performance-metrics)
# Install and Setup
The Answers Javascript API Library does not need to be installed locally. Instead, it can be used
with script tags on a webpage. The instructions below explain how to do this; they will walk you through
adding the Answers stylesheet, JS library, and an intialization script to an HTML page.
After doing this, you can view your page in the browser.
Include the Answers CSS
```html
```
Add the Javascript library and placeholder elements for [Answers components](#component-usage).
```html
```
Add an initialization script with an apiKey or token, experienceKey and onReady function. In the example below, we've initialized two
basic components with apiKey: [SearchBar](#searchbar-component) and [UniversalResults](#universal-results-component).
```js
function initAnswers() {
ANSWERS.init({
apiKey: '', // See [1]
experienceKey: '',
onReady: function() {
ANSWERS.addComponent('SearchBar', {
container: '#SearchBarContainer',
});
ANSWERS.addComponent('UniversalResults', {
container: '#UniversalResultsContainer',
});
}
})
}
```
ANSWERS.init() returns a promise which optionally allows components to be added using promise syntax
```js
function initAnswers() {
ANSWERS.init({
apiKey: '', // See [1]
experienceKey: '',
}).then(function() {
ANSWERS.addComponent('SearchBar', {
container: '#SearchBarContainer',
});
ANSWERS.addComponent('UniversalResults', {
container: '#UniversalResultsContainer',
});
});
}
```
[1] Learn more about [getting your API key](https://developer.yext.com/docs/guides/get-started/).
# ANSWERS.init Configuration Options
The configuration provided here is configuration that is shared across components.
```js
function initAnswers() {
ANSWERS.init({
// Required*, your Yext Answers API key. *Do NOT provide apiKey if token is used.
apiKey: '',
// Required*, custom auth token. *Do NOT provide token if apiKey is used.
token: '',
// Required, the key used for your Answers experience
experienceKey: '',
// Optional, indicates the environment to run Answers experience in ('production' or 'sandbox')
environment: '',
// Optional, see below
idMethod: '',
},
// Optional, initialize components here, invoked when the Answers component library is loaded/ready.
// If components are not added here, they can also be added when the init promise resolves
onReady: function() {},
// Optional*, Yext businessId, *required to send analytics events
businessId: 'businessId',
// Optional, if false, the library will not fetch pre-made templates. Only use change this to false if you provide a
// template bundle in the `templateBundle` config option or implement custom renders for every component
useTemplates: true,
// Optional, additional templates to register with the renderer
templateBundle: {},
// Optional, provide configuration for each vertical that is shared across components, see Vertical Pages Configuration below
verticalPages: [],
// Optional, search specific settings, see Search Configuration below
search: {},
// Optional, vertical no results settings, see Vertical No Results below
noResults: {},
// Optional, the locale will affect how queries are interpreted and the results returned. Defaults to 'en'.
locale: 'en',
// Optional, the Answers Experience version to use for api requests
experienceVersion: 'PRODUCTION',
// Optional, prints full Answers error details when set to `true`. Defaults to false.
debug: false,
// Optional, If true, the search session is tracked. If false, there is no tracking. Defaults to true.
sessionTrackingEnabled: true,
// Optional, invoked when the state of any component changes
onStateChange: function() {},
// Optional, analytics callback after a vertical search, see onVerticalSearch Configuration for additional details
onVerticalSearch: function() {},
// Optional, analytics callback after a universal search, see onUniversalSearch Configuration for additional details
onUniversalSearch: function() {},
// Optional, opt-out of automatic css variable resolution on init for legacy browsers
disableCssVariablesPonyfill: false,
// Optional, the analytics key describing the Answers integration type. Accepts 'STANDARD', 'OVERLAY', or arbitrary strings. Defaults to 'STANDARD'
querySource: 'STANDARD',
// Optional, additional values for the HTTP headers listed below. Headers other than those listed below will be ignored.
additionalHttpHeaders: {
// Additional key-value pairs to send in the Client-SDK header.
// ANSWERS_CORE and ANSWERS_SEARCH_UI_SDK are automatically set and cannot be overwritten.
'Client-SDK': {}
}
})
}
```
## Vertical Pages Configuration
Below is a list of configuration options related to vertical pages in navigation and no results, used in the [base configuration](#answersinit-configuration-options) above.
```js
verticalPages: [
{
// Required, the label for this page
label: 'Home',
// Required, the link to this page
url: './index.html',
// Optional*, the verticalKey, *required for vertical pages (must omit this property for universal)
verticalKey: 'locations',
// Optional, the icon name to use in no results, defaults to no icon
icon: 'star',
// Optional, the URL icon to use in no results, defaults to no icon
iconUrl: '',
// Optional, if true, will show this page first in the Navigation Component, defaults to false
isFirst: false,
// Optional, if true, will add a special styling to this page in the Navigation Component, defaults to false
isActive: false,
// Optional, if true, hide this tab in the Navigation Component, defaults to false
hideInNavigation: false,
},
...
]
```
## Search Configuration
Below is a list of configuration options related to search, used in the [base configuration](#answersinit-configuration-options) above.
```js
search: {
// Optional, the vertical key to use for searches
verticalKey: 'verticalKey',
// Optional, the number of results to display per page, defaults to 20. Maximum is 50.
limit: '20',
// Optional, an object containing the number of results to display per vertical key. Minimum is 1. Maximum is 50.
// The default value is determined by the API at 10.
universalLimit: {
verticalKey1: 20,
verticalKey2: 49
},
// Optional, Vertical Pages only, a default search to use on page load when the user hasn't provided a query
defaultInitialSearch: 'What is Yext Answers?',
},
```
## Vertical No Results Configuration
Below is a list of configuration options related to no results on Vertical Pages, used in the [base configuration](#answersinit-configuration-options) above.
```js
noResults: {
// Optional, whether to display all results for the Vertical when a query has no results, defaults to false
displayAllResults: false,
// Optional, a custom template for the no results card
template: '',
},
```
## onVerticalSearch Configuration
The onVerticalSearch Configuration is a function, used in the [base configuration](#answersinit-configuration-options) above.
It allows you to send an analytics event each time a search is run on a Vertical page. This function should take in one parameter, `searchParams`, which contains information about the search, and return the desired analytics event.
Like all Answers Javascript API Library analytics, this will only work if there is a businessId in the ANSWERS.init.
The search information exposed in `searchParams` is shown below.
```js
function (searchParams) => {
/**
* Vertical key used for the search.
* @type {string}
*/
const verticalKey = searchParams.verticalKey;
/**
* The string being searched for.
* @type {string}
*/
const queryString = searchParams.queryString;
/**
* The total number of results found.
* @type {number}
*/
const resultsCount = searchParams.resultsCount;
/**
* Either 'normal' or 'no-results'.
* @type {string}
*/
const resultsContext = searchParams.resultsContext;
let analyticsEvent = new ANSWERS.AnalyticsEvent('ANALYTICS_EVENT_TYPE');
analyticsEvent.addOptions({
label: 'Sample analytics event',
searcher: 'VERTICAL',
query: queryString,
resultsCount: resultsCount,
resultsContext: resultsContext,
});
return analyticsEvent;
},
}),
```
## onUniversalSearch Configuration
The onUniversalSearch Configuration is a function, used in the [base configuration](#answersinit-configuration-options) above.
It allows you to send an analytics event each time a search is run
on a Universal page. This function should take in one parameter, `searchParams`, which contains information about the
search, and return the desired analytics event.
Like all Answers Javascript API Library analytics, this will only work if there is a businessId in the ANSWERS.init.
The search information exposed in `searchParams` is shown below.
```js
function (searchParams) => {
/**
* The string being searched for.
* @type {string}
*/
const queryString = searchParams.queryString;
/**
* The total number of results found.
* @type {number}
*/
const sectionsCount = searchParams.sectionsCount;
/**
* A map containing entries of the form:
* { totalResultsCount: 150, displayedResultsCount: 10}
* for each returned vertical. The totalResultsCount indicates how many results
* are present in the vertical. The displayResultsCount indicates how many of
* those results are actually displayed.
* @type {Object}
*/
const resultsCountByVertical = searchParams.resultsCountByVertical;
let analyticsEvent = new ANSWERS.AnalyticsEvent('ANALYTICS_EVENT_TYPE');
analyticsEvent.addOptions({
type: 'ANALYTICS_EVENT_TYPE',
label: 'Sample analytics event',
searcher: 'UNIVERSAL',
query: queryString
sectionsCount: sectionsCount,
});
return analyticsEvent;
},
}),
```
## Visitor Configuration
Below is a list of configuration attributes related to a visitor, used in the [base configuration](#answersinit-configuration-options) above.
The visitor object ties a user's identity to their searches and actions. The visitor can also be set or changed using the `ANSWERS.setVisitor` function.
```js
visitor: {
// Required, the ID associated with the user. This will be the yextUserId if Yext Auth is used. Max of 64 characters.
id: '123919',
// Optional, the method used to generate the visitor ID. Max of 16 characters.
idMethod: 'YEXT_USER',
},
```
# Component Usage
The Answers Component Library exposes an easy to use interface for adding and customizing various types of UI components on your page.
## What is a Component?
At a high level, components are the individual pieces of an Answers page. The Answers Javascript API Library comes with many types of components. Each component is an independent, reusable piece of code. A component fills an HTML element container that the implementer provides on the page. Components are updated from their config, the config from the ANSWERS.init, and potentially an API response.
Each type of Component has its own custom configurations. Additionally, all components share the base configuration options defined above. We will provide a brief description below of what each component does, along with describing how it can be configured.
## Base Component Configuration
Every component has the same base configuration options.
```js
{
// Required, the selector for the container element where the component will be injected
container: 'container',
// Optional, a unique name for the component
name: 'name',
// Optional, an additional, custom HTML classname for the component. The component will also
// have a classname of 'yxt-Answers-component' applied.
class: 'class',
// Optional, handlebars template or HTML to override built-in handlebars template for the component
template: 'template',
// Optional, override render function
render: function(data) {},
// Optional, a hook for transforming data before it gets sent to render
transformData: function(data) {},
// Optional, invoked when the HTML is mounted to the DOM, this will not override any built-in onMount function for a component
onMount: function(data) {},
// Optional, invoked when the HTML is mounted to the DOM, this will override any built-in onMount function for a component
onMountOverride: function(data) {},
// Optional, additional properties to send with every analytics event
analyticsOptions: {},
}
```
## Adding a Component to Your Page
Adding a component to your page is super easy!
You can add many different [types](#types-of-built-in-components) of components to your page.
Each component supports the base configuration options above, as well as their own unique configurations.
To start, every component requires an HTML container.
```html
```
Then, you can add a component to your page through the ANSWERS add interface. You need to call `addComponent` from `onReady`.
This is an example of the `SearchBar`. See [Types of Built-in Components](#types-of-built-in-components) below.
```js
ANSWERS.addComponent('SearchBar', {
container: '.search-container'
})
```
## Removing Components
If you'd like to remove a component and all of its children, you can do it. Simply `ANSWERS.removeComponent()`:
```js
ANSWERS.addComponent('SearchBar', {
container: '.search-container',
name: 'MySearchBar'
})
ANSWERS.removeComponent('MySearchBar');
```
# Types of Built-in Components
## SearchBar Component
The SearchBar component is the main entry point for search querying. It provides the input box, where the user
types their query, as well as the autocomplete behavior.
```html
```
If the `verticalKey` config option is omitted, the SearchBar will perform Universal searches. Universal
searches return results across multiple Verticals; Vertical searches search within one Vertical. Additionally, Universal search and Vertical search provide a different way of auto complete.
```js
ANSWERS.addComponent('SearchBar', {
// Required, the selector for the container element where the component will be injected
container: '.search-query-container',
// Required* for Vertical pages, omit for Universal pages
verticalKey: '',
// Optional, title is not present by default
title: 'Search my Brand',
// Optional, the initial query string to use for the input box
query: 'query',
// Optional, defaults to 'Conduct a search'
labelText: 'What are you looking for?',
// Optional, used for labeling the submit button, also provided to the template
submitText: 'Submit',
// Optional, used for labeling the clear button, also provided to the template
clearText: 'Clear',
// Optional, used to specify a different built-in icon for the submit button. Defaults to Animated Magnifying glass when CSS is included.
submitIcon: 'iconName',
// Optional, a url for a custom icon for the submit button. Defaults to Animated Magnifying glass when CSS is included.
customIconUrl: 'path/to/icon',
// Optional, the query text to show as the first item for auto complete
promptHeader: 'Header',
// Optional, no default
placeholderText: 'Start typing...',
// Optional, auto focuses the search bar. Defaults to false
autoFocus: false,
// Optional, opens the autocomplete suggestions on page load. Defaults to false. Requires autoFocus to be set to true
autocompleteOnLoad: false,
// Optional, allows a user to conduct an empty search. Should be set to true if the defaultInitialSearch is "".
allowEmptySearch: false,
// Optional, defaults to 300ms (0.3 seconds)
searchCooldown: 2000,
// Optional, asks the user for their geolocation when "near me" intent is detected
promptForLocation: true,
// Optional, displays an "x" button to clear the current query when true
clearButton: true,
// Optional, redirect search query to url
redirectUrl: 'path/to/url',
// Optional, target frame for the redirect url, defaults to current frame. Expects a valid target: "_blank", "_self", "_parent", "_top" or the name of a frame
redirectUrlTarget: '_self',
// Optional, defaults to native form node within container
formSelector: 'form',
// Optional, defaults to true. When true, a form is used as the query submission context.
// Note that WCAG compliance is not guaranteed if a form is not used as the context.
useForm: 'true',
// Optional, the input element used for searching and wires up the keyboard interaction
inputEl: '.js-yext-query',
// Optional, options to pass to the geolocation api, which is used to fetch the user's current location.
// https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions
geolocationOptions: {
// Optional, whether to improve accuracy at the cost of response time and/or power consumption, defaults to false.
enableHighAccuracy: false,
// Optional, the maximum amount of time (in ms) a geolocation call is allowed to take before defaulting, defaults to 1 second.
timeout: 1000,
// Optional, the maximum amount of time (in ms) to cache a geolocation call, defaults to 5 minutes.
maximumAge: 300000,
},
// Optional, options for an alert when the geolocation call fails.
geolocationTimeoutAlert: {
// Optional, whether to display a window.alert() on the page, defaults to false.
enabled: false,
// Optional, the message in the alert. Defaults to the below
message: "We are unable to determine your location"
},
// Optional, functions invoked when certain events occur
customHooks: {
// Optional, a callback invoked when the clear search button is clicked
onClearSearch: function() {},
// Optional, a function invoked when a search is conducted. The search terms are passed in as a string
onConductSearch: function(searchTerms) {}
},
// Optional, options to pass to the autocomplete component
autocomplete: {
// Optional, boolean used to hide the autocomplete when the search input is empty (even if the
// input is focused). Defaults to false.
shouldHideOnEmptySearch: false,
// Optional, callback invoked when the autocomplete component changes from open to closed.
onClose: function() {},
// Optional, callback invoked when the autocomplete component changes from closed to open.
onOpen: function() {},
// Optional, a string array of custom prompts to include in the autocomplete dropdown.
customPrompts: []
},
// Optional, options for loading indicator on seachbar
loadingIndicator: {
// Optional, whether to include a loading indicator on seachbar
display: false,
// Optional, use custom icon url instead of the default loading indicator animation
iconUrl: ""
},
// Optional, options for voice search feature on seachbar
voiceSearch: {
// Optional, whether or not voice search is enabled
enabled: false,
// Optional, provide custom mic icon url to override the voice start icon
customMicIconUrl: "",
// Optional, provide custom listening icon url to override the voice stop icon
customListeningIconUrl: ""
}
})
```
## Direct Answer Component
This component is for Universal pages only.
The Direct Answer Component will render the BEST result, if found, based on the query.
```html
```
```js
ANSWERS.addComponent('DirectAnswer', {
// Required, the selector for the container element where the component will be injected
container: '.direct-answer-container',
// Optional, a custom direct answer card to use, which is the default when there are no matching card overrides.
// See the Custom Direct Answer Card section below.
defaultCard: 'MyCustomDirectAnswerCard',
// Optional, the selector for the form used for submitting the feedback
formEl: '.js-directAnswer-feedback-form',
// Optional, the selector to bind ui interaction to for reporting
thumbsUpSelector: '.js-directAnswer-thumbUp',
// Optional, the selector to bind ui interaction to for reporting
thumbsDownSelector: '.js-directAnswer-thumbDown',
// Optional, the display text for the View Details click to action link
viewDetailsText: 'View Details',
// Optional, the screen reader text for positive feedback on the answer
positiveFeedbackSrText: 'This answered my question',
// Optional, the screen reader text for negative feedback on the answer
negativeFeedbackSrText: 'This did not answer my question',
// Optional, the footer text to display on submission of feedback
footerTextOnSubmission: 'Thank you for your feedback!',
// Optional, specify card types and overrides based on the direct answer type. The first matching cardOverride will be used, otherwise the cardType is used
types: {
'FEATURED_SNIPPET': {
cardType: "documentsearch-standard",
cardOverrides: [
{
fieldName: 'description',
entityType: 'ce_menuItem',
cardType: 'MenuItemDescriptionDirectAnswer'
},
{
fieldName: 'description',
entityType: 'ce_menuItem',
fieldType: 'rich_text'
cardType: 'MenuItemDescriptionDirectAnswer'
}
]
},
'FIELD_VALUE': {
cardType: "allfields-standard",
cardOverrides: [
{
cardType: 'MenuItemDescriptionDirectAnswer',
fieldName: 'description',
entityType: 'ce_menuItem',
fieldType: 'rich_text'
}
]
}
}
// DEPRECATED: use the types option instead
// Optional, card overrides that allow you to specify a specific direct answers card depending on the fieldName, entityType, and fieldType of the direct answer. The first matching card will be used, otherwise defaultCard will be used.
cardOverrides: [
{
cardType: 'MenuItemDescriptionDirectAnswer',
fieldName: 'description',
entityType: 'ce_menuItem',
fieldType: 'rich_text'
},
{
cardType: 'DeliveryHoursDirectAnswer',
fieldName: 'c_deliveryHours'
},
{
cardType: 'PhoneDirectAnswer',
fieldType: 'phone'
}
]
})
```
## Creating a Custom Direct Answer Card
You can customize the look and behavior of your Direct Answer by creating a custom Direct Answer card.
A custom Direct Answer card is given the same data as the built-in card.
That data will look something like the below:
```js
{
type: "FIELD_VALUE",
answer: {
entityName: "Entity Name",
fieldName: "Phone Number",
fieldApiName: "mainPhone",
value: "+11234567890",
fieldType: "phone"
},
relatedItem: {
verticalConfigId: 'people',
data: {
id: "Employee-2116",
type: "ce_person",
fieldValues: {
description: "This is the description field.",
name: "First Last",
firstName: "First",
lastName: "Last",
mainPhone: "+1234567890",
}
}
}
}
```
A custom Direct Answer card needs a corresponding template.
This can be added either inline by changing the component's constructor to:
```js
constructor(config, systemConfig) {
super(config, systemConfig);
this.setTemplate(`
your template here `)
}
```
Or by including a custom template bundle, and adding:
```js
static defaultTemplateName () {
return 'CustomDirectAnswerTemplate';
}
```
Where 'CustomDirectAnswerTemplate' is the name the template is registered under.
We will use the following template for our example card.
```hbs
{{type}}
{{#each customValue}}
{{#if url}}
{{> valueLink }}
{{else}}
{{{this}}}
{{/if}}
{{/each}}
{{> feedback}}
{{#*inline 'feedback'}}
{{/inline}}
{{#*inline 'valueLink'}}
{{{displayText}}}
{{/inline}}
```
This specific example needs some css to flip the thumbs up icon the right way.
```css
.customDirectAnswer-thumbsUpIcon svg {
transform: rotate(180deg);
}
```
This is the javascript class for our custom Direct Answer card.
It applies custom formatting to the Direct Answer, registers analytics events
to the thumbs up/down icons, and passes custom event options into the template.
```js
class CustomDirectAnswerClass extends ANSWERS.Component {
constructor(config, systemConfig) {
// If you need to override the constructor, make sure to call super(config, systemConfig) first.
super(config, systemConfig);
// For simplicity's sake, we set this card's template using setTemplate(), as opposed to
// a custom template bundle.
this.setTemplate(`
your template here `)
}
/**
* setState() lets you pass variables directly into your template.
* Here, data is the directAnswer data from the query.
* Below, we pass through a custom direct answers value, customValue.
* @param {Object} data
* @returns {Object}
*/
setState(data) {
const { type, answer, relatedItem } = data;
const associatedEntityId = data.relatedItem && data.relatedItem.data && data.relatedItem.data.id;
const verticalConfigId = data.relatedItem && data.relatedItem.verticalConfigId;
return super.setState({
...data,
customValue: this.getCustomValue(answer),
eventType: 'CUSTOM_EVENT',
eventOptions: {
searcher: 'UNIVERSAL',
verticalConfigId: verticalConfigId,
entityId: associatedEntityId,
}
});
}
/**
* onMount() lets you register event listeners. Here, we register the thumbs up and thumbs
* down buttons to fire an analytics event on click.
*/
onMount() {
const thumbsUpIcon = this._container.querySelector('.js-customDirectAnswer-thumbsUpIcon');
const thumbsDownIcon = this._container.querySelector('.js-customDirectAnswer-thumbsDownIcon');
thumbsUpIcon.addEventListener('click', () => this.reportQuality(true));
thumbsDownIcon.addEventListener('click', () => this.reportQuality(false));
}
/**
* reportQuality() sends an analytics event (either THUMBS_UP or THUMBS_DOWN).
* @param {boolean} isGood true if the answer is what you were looking for
*/
reportQuality(isGood) {
const eventType = isGood === true ? 'THUMBS_UP' : 'THUMBS_DOWN';
const event = new ANSWERS.AnalyticsEvent(eventType).addOptions({
directAnswer: true
});
this.analyticsReporter.report(event);
}
/**
* Formats a Direct Answer value based on its fieldType.
* @param {Object} answer the answer property in the directAnswer model
* @returns {string}
*/
formatValue(answer) {
const { fieldType, value } = answer;
switch (fieldType) {
case 'phone':
return {
url: 'http://myCustomWebsite.com/?mainPhone=' + value,
displayText: value,
};
case 'rich_text':
return ANSWERS.formatRichText(value);
case 'single_line_text':
case 'multi_line_text':
default:
return value;
}
}
/**
* Computes a custom Direct Answer. If answer.value is an array, this method
* formats every value in the array and returns it, otherwise it just formats the single
* given value.
* @param {Object} answer
* @returns {Array}
*/
getCustomValue(answer) {
if (Array.isArray(answer.value)) {
return answer.value.map(value => this.formatValue(answer))
} else {
return [ this.formatValue(answer) ];
}
}
/**
* The name of your custom direct answer card. THIS is the value you will use in any config,
* such as defaultCard, when you want to specify this custom Direct Answer card.
* @returns {string}
*/
static get type() {
return 'MyCustomDirectAnswerCard';
}
}
// Don't forget to register your Direct Answer card within the SDK. Otherwise the SDK won't recognize your card name!
ANSWERS.registerComponentType(CustomDirectAnswerClass);
```
## Universal Results Component
The Universal Results component will render the results of a query,
across all configured verticals, with one section per vertical.
```html
```
```js
ANSWERS.addComponent('UniversalResults', {
// Required, the selector for the container element where the component will be injected
container: '.universal-results-container',
// Settings for the applied filters bar in the results header. These settings can be overriden in the
// "config" option below on a per-vertical basis.
appliedFilters: {
// If true, show any applied filters that were applied to the universal search. Defaults to true
show: true,
// If appliedFilters.show is true, whether to display the field name of an applied filter, e.g. "Location: Virginia" vs just "Virginia". Defaults to false.
showFieldNames: false,
// If appliedFilters.show is true, this is list of filters that should not be displayed.
// By default, builtin.entityType will be hidden
hiddenFields: ['builtin.entityType'],
// The character that separates the count of results (e.g. “1-6”) from the applied filter bar. Defaults to '|'
resultsCountSeparator: '|',
// Whether to display the change filters link in universal results. Defaults to false.
showChangeFilters: false,
// The text for the change filters link. Defaults to 'change filters'.
changeFiltersText: 'change filters',
// The character that separates each field (and its associated filters) within the applied filter bar. Defaults to '|'
delimiter: '|',
// The aria-label given to the applied filters bar. Defaults to 'Filters applied to this search:'.
labelText: 'Filters applied to this search:',
},
// Optional, configuration for each vertical's results
config: {
people: { // The verticalKey
card: {
// Configuration for the cards in this vertical, see Cards
},
// Optional: A custom handlebars template for this section
template: '
Custom section template ',
// The title of the vertical
// Defaults to the vertical key, in this example 'people'
title: 'People',
// Icon to display to the left of the title. Must be one of our built-in icons, defaults to 'star'
icon: 'star',
// The url for both the viewMore link and the change-filters link. Defaults to '{{VERTICAL_KEY}}.html',
// in this case that is 'people.html'
url: 'people.html',
// Whether to display a view more link. Defaults to true
viewMore: true,
// The text for the view more link, if viewMore is true. Defaults to 'View More'
viewMoreLabel: 'View More!',
// Config for the applied filters bar in the results header.
appliedFilters: {
// Same as appliedFilters settings above. Settings specified here will override any top level settings.
},
// If true, display the count of results at the very top of the results. Defaults to false.
showResultCount: true,
// If true, display the total number of results. Defaults to true
// Optional, whether to use the AccordionResults component instead of VerticalResults for this vertical
useAccordion: false,
// Optional, whether to include a map with this vertical's results, defaults to false
includeMap: true,
// Optional*, if includeMap is true, this is required
mapConfig: {
// Required, either 'mapbox' or 'google', not case sensitive
mapProvider: 'google',
// Required, API key for the map provider
apiKey: '<<< enter your api key here >>>',
// ... Optional, any other config for the Map Component, find more info in the section "Map Component"
},
// Optional, override the render function for each result in this vertical
renderItem: function(data) {},
// Optional, override the handlebars template for each item in this vertical
itemTemplate: `my item {{name}}`,
// DEPRECATED, please use viewMoreLabel instead. viewAllText is a synonym for viewMoreLabel, where viewMoreLabel takes precedence over viewAllText. Defaults to 'View More'.
viewAllText: 'View All Results For Vertical'
}
},
// Optional, override the render function for each item in the result list
renderItem: function(data) {},
// Optional, override the handlebars template for each item in the result list
itemTemplate: `my item {{name}}`,
})
```
## Vertical Results Component
The Vertical Results component shares all the same configurations from Universal Results, but you don't need to specifiy a config or context. You may limit the number of search results returned, with a maximum of 50.
You define all the options at the top level object.
```html
```
```js
ANSWERS.addComponent('VerticalResults', {
// Required, the selector for the container element where the component will be injected
container: '.vertical-results-container',
// Optional, function to give each result item custom rendering
renderItem: () => {},
// Optional, string to give custom template to result item
itemTemplate: `
Custom template `,
// Optional, set a max number of columns to display at the widest breakpoint. Possible values are 1, 2, 3 or 4, defaults to 1
maxNumberOfColumns: 1,
// Optional, a modifier that will be appended to a class on the results list like this `yxt-Results--{modifier}`
modifier: '',
// Optional, whether to hide the default results header that VerticalResults provides. Defaults to false.
hideResultsHeader: false,
// Optional, the card used to display each individual result, see the Cards section for more details,
card: {
// Optional, The type of card, built-in types are: 'Standard', 'Accordion', and 'Legacy'. Defaults to 'Standard'
cardType: 'Standard',
// Optional, see Data Mappings for more details
dataMappings: () => {},
// Optional, see Calls To Action for more details
callsToAction: () => []
},
// Optional, configuration for what to display when no results are found.
noResults: {
// Optional, used to specify a custom template for the no results card, defaults to a built-in template.
template: ' No results found! Try again? ',
// Optional, whether to display all results in the vertical when no results are found. Defaults to false, in which case only the no results card will be shown.
displayAllResults: false
},
/**
* NOTE: The config options below are DEPRECATED.
* They will still work as expected, and the defaults will still be applied,
* but future major versions of the SDK will remove them.
* We recommend setting hideResultsHeader to true, and using the VerticalResultsCount and AppliedFilters components instead.
*/
// Optional, whether to display the total number of results, default true
showResultCount: true,
// Optional, a custom template for the results count. You can specify the variables resultsCountStart, resultsCountEnd, and resultsCount.
resultsCountTemplate: '
{{resultsCountStart}} - {{resultsCountEnd}} of {{resultsCount}}',
// Configuration for the applied filters bar in the header.
appliedFilters: {
// If true, show any applied filters that were applied to the vertical search. Defaults to true
show: true,
// If appliedFilters.show is true, whether to display the field name of an applied filter, e.g. "Location: Virginia" vs just "Virginia". Defaults to false.
showFieldNames: false,
// If appliedFilters.show is true, this is list of filters that should not be displayed.
// By default, builtin.entityType will be hidden
hiddenFields: ['builtin.entityType'],
// The character that separates the count of results (e.g. “1-6”) from the applied filter bar. Defaults to '|'
resultsCountSeparator: '|',
// If the filters are shown, whether or not they should be removable buttons. Defaults to false.
removable: false,
// The character that separates each field (and its associated filters) within the applied filter bar. Defaults to '|'
delimiter: '|',
// The aria-label given to the applied filters bar. Defaults to 'Filters applied to this search:'.
labelText: 'Filters applied to this search:',
// The aria-label given to the removable filter buttons.
removableLabelText: 'Remove this filter'
}
})
```
## Vertical Results Count Component
The results count component displays the current results count on a vertical page.
```js
ANSWERS.addComponent('VerticalResultsCount', {
container: '.results-count-container',
noResults: {
// Optional, whether the results count should be visible when displaying no results.
// Defaults to false.
visible: false
}
});
```
## Cards
Cards are used in Universal/Vertical Results for configuring the UI for a result on a per-item basis.
Cards take in a dataMappings attribute, which contains configuration for the card, and a callsToAction
attribute, which contains config for any callToAction buttons in the card.
callsToAction config is common throughout all cards, whereas different cards such as Standard vs BigImage
have specialized configuration depending on the card. See [Calls To Action](#Calls-To-Action)
There are three built-in cards, the [Standard Card](#Standard-Card), the [Accordion Card](#Accordion-Card)
and [Legacy Card](#Legacy-Card).
## Calls To Action
callsToActions are specified as either an array of CTA configs, or a function that returns
an array of CTA configs. An array of CTA configs is an object of either static config options
or functions that return the desired config option.
Examples are detailed below.
Note: A CTA without both a label and icon will not be rendered.
1. an array of static CTA config objects
```js
const callsToAction = [{
// Label below the CTA icon, default null
label: 'cta label',
// Icon name for the CTA that is one of the built-in icons, defaults to undefined (no icon). If your icon
// is not recognized it will default to 'star'.
icon: 'star',
// URL to a custom icon for the cta. This takes priority over icon if both are present, default is
// no icon url.
iconUrl: 'https://urltomyicon.com/customicon.gif',
// Click through url for the icon and label
// Note, a protocol like https:// is required here.
url: 'https://yext.com',
// Analytics event that should fire, defaults to 'CTA_CLICK'. Other events outlined in the Analytics section.
analytics: 'CTA_CLICK',
// The target attribute for the CTA link, defaults to '_blank'. To open in a new window use '_blank'
target: '_blank',
// The eventOptions needed for the event to fire. Either a valid json string, an object, or a function that
// takes in the result data response.
// By default, if no event options are specified the SDK will try to add verticalKey, entityId, and searcher options
// to the analytics event.
eventOptions: result => {
return {
// The vertical key for the CTA. If unspecified, this defaults to the vertical key this cta is a part of
verticalKey: 'people',
// The entity id of the result this cta is a part of, defaults to the entityId field in Knowledge Graph
entityId: result.id,
// If the CTA is inside a vertical search, defaults to the value "VERTICAL",
// if is inside a universal search, defaults to the value "UNIVERSAL"
searcher: 'VERTICAL'
};
}
}]
```
2. as a function that returns a cta config object.
NOTE: we do not allow multiple nested functions, to avoid messy user configurations.
```js
const callsToAction = item => [{
label: item._raw.name,
url: 'https://yext.com',
analyticsEventType: 'CTA_CLICK',
target: '_blank',
icon: 'briefcase',
eventOptions: `{ "verticalKey": "credit-cards", "entityId": "${item._raw.id}", "searcher":"UNIVERSAL", "ctaLabel": "cards"}`
}, {
label: 'call now',
url: 'https://maps.google.com',
analyticsEventType: 'CTA_CLICK',
target: '_blank',
icon: 'phone',
eventOptions: `{ "verticalKey": "credit-cards", "entityId": "${item._raw.id}", "searcher": "UNIVERSAL", "ctaLabel": "cards"}`
}]
```
3. Each individual field in a CTA config can also be a function that operates on the result item.
```js
const callsToAction = item => [{
label: item => item._raw.name,
url: 'https://yext.com',
analyticsEventType: 'CTA_CLICK',
target: '_self',
icon: 'briefcase',
eventOptions: `{ "verticalKey": "credit-cards", "entityId": "${item._raw.id}", "searcher": "UNIVERSAL", "ctaLabel": "cards"}`
}]
```
callsToActions can then be included in a card object like so:
```js
ANSWERS.addComponent('VerticalResults', {
/* ...other vertical results config... */
card: {
/* ...other card config...*/
callsToAction: item => [{
label: item => item._raw.name,
url: 'https://yext.com',
}]
}
/* ...other vertical results config... */
})
```
## Data Mappings
The dataMappings config option define how a card's attributes, such as title and details, will be rendered.
They can be configured either through a function that returns a dataMappings object
or a static dataMappings object.
Each attribute of a dataMappings object is also either a function or a static value.
Below is an example of dataMappings as function.
```js
ANSWERS.addComponent('VerticalResults', {
/* ...other vertical results config... */
card: {
/* ...other card config...*/
dataMappings: item => ({
title: item.name,
subtitle: `Department: ${item.name} `,
details: item.description,
image: item.headshot ? item.headshot.url : '',
url: 'https://yext.com',
showMoreLimit: 500,
showMoreText: "show more",
showLessText: "put it back",
target: '_blank'
})
}
/* ...other vertical results config... */
})
```
And below is an example of dataMappings as an object with functions inside it.
You can use both static attributes and function attributes together.
```js
ANSWERS.addComponent('VerticalResults', {
/* ...other vertical results config... */
card: {
/* ...other card config...*/
dataMappings: {
title: item => item.name,
subtitle: item => `Department: ${item.name} `,
details: item => item.description,
image: item => item.headshot ? item.headshot.url : '',
url: 'https://yext.com',
showMoreLimit: 500,
showMoreText: 'show more',
showLessText: 'put it back',
target: '_blank'
}
}
/* ...other vertical results config... */
})
```
## Standard Card
The data mappings for a Standard Card has these attributes
```js
const dataMappings = item => {
return {
// Title for the card, defaults to the name of the entity
title: item.title,
// Subtitle, defaults to null
subtitle: `Department: ${item.name} `,
// Details, defaults to the entity's description
details: item.description,
// Image to display, defaults to null
image: item.headshot ? item.headshot.url : '',
// Url for the title/subtitle, defaults to the entity's website url
// Note, a protocol like https://yext.com is required, as opposed to just yext.com
url: item.link || item.website,
// Character limit to hide remaining details and display a show more button, defaults to no limit.
showMoreLimit: 350,
// Text for show more button, defaults to 'Show More'
showMoreText: 'show more',
// Text for show less button, defaults to 'Show Less'
showLessText: 'put it back',
// The target attribute for the title link, defaults to '_self'. To open in a new window use '_blank'
target: '_blank',
// Whether to show the ordinal of this card in the results, i.e. first card is 1 second card is 2,
// defaults to false
showOrdinal: false,
// A tag to display on top of an image, always overlays the image, default no tag
tagLabel: 'On Sale!'
};
}
```
## Accordion Card
The data mappings for an Accordion Card has these attributes
```js
const dataMappings = item => {
return {
// Title for the card, defaults to the name of the entity
title: item.title,
// Subtitle, defaults to null
subtitle: `Department: ${item.name} `,
// Details, defaults to the entity's description
details: item.description,
// Whether the first Accordion Card shown in vertical/universal results should be open on page load, defaults to false
expanded: false
};
}
```
## Legacy Card
The Legacy Card is very similar to the Standard Card, but with the legacy DOM structure and class names
from before v0.13.0. New users should not use the Legacy Card; instead, use the Standard Card. Features
added after v0.13.0 may not work with the Legacy Card.
The data mappings for a legacy card has these attributes
```js
const dataMappings = item => {
return {
// Title for the card, defaults to the name of the entity
title: item.title,
// Subtitle, defaults to null
subtitle: `Department: ${item.name} `,
// Details, defaults to the entity's description
details: item.description,
// Image to display, defaults to null
image: item.headshot ? item.headshot.url : '',
// Url for the title/subtitle, defaults to the entity's website url
url: item.link || item.website,
// The target attribute for the title link, defaults to '_self'. To open in a new window use '_blank'
target: '_blank',
// Whether to show the ordinal of this card in the results, i.e. first card is 1 second card is 2,
// defaults to false
showOrdinal: false
};
}
```
## Pagination Component
This component is only for Vertical pages.
The Pagination component allows users to page through vertical search results.
```html
```
```js
ANSWERS.addComponent('Pagination', {
// Required, the selector for the container element where the component will be injected
container: '.pagination-component',
// Required*, the vertical for pagination, *if omitted, will fall back to the search base config
verticalKey: 'verticalKey',
// Optional, the maximum number of pages visible to non-mobile users. Defaults to 1.
maxVisiblePagesDesktop: 1,
// Optional, the maximum number of pages visible to mobile users. Defaults to 1.
maxVisiblePagesMobile: 1,
// Optional, ensure that the page numbers for first and last page are always shown. Not recommended to use with showFirstAndLastButton. Defaults to false.
pinFirstAndLastPage: false,
// Optional, display double-arrows allowing users to jump to the first and last page of results. Defaults to true.
showFirstAndLastButton: true,
// Optional, label for a page of results. Defaults to 'Page'.
pageLabel: 'Page',
// Optional, configuration for the pagination behavior when a query returns no results
noResults: {
// Optional, whether pagination should be visible when displaying no results.
// Defaults to false.
visible: false
},
// Function invoked when a user clicks to change pages. By default, scrolls the user to the top of the page.
onPaginate: (newPageNumber, oldPageNumber, totalPages) => {},
// DEPRECATED, please use showFirstAndLastButton instead.
// Display a double arrow allowing users to jump to the first page of results. Defaults to showFirstAndLastButton.
showFirst: true,
// DEPRECATED, please use showFirstAndLastButton instead.
// Display a double arrow allowing users to jump to the last page of results. Defaults to showFirstAndLastButton.
showLast: true,
});
```
## FilterBox Component
This component is only for Vertical pages.
The FilterBox component shows a list of filters to apply to a search.
```html
```
```js
ANSWERS.addComponent('FilterBox', {
// Required, the selector for the container element where the component will be injected
container: '.filters-container',
// Required, list of filter component configurations
filters: [
{
type: 'FilterOptions',
control: 'multioption',
options: [
{
label: 'Open Now',
field: 'c_openNow',
value: true
},
{
label: 'Dog Friendly',
field: 'c_dogFriendly',
value: true
},
{
label: 'Megastores',
field: 'c_storeType',
value: 'Megastore'
}
]
}
],
// Optional, title to display above the filter
title: 'Filters',
// Optional, show number of results for each filter
showCount: true,
// Optional, execute a new search whenever a filter selection changes. If true, the Apply and Reset buttons will not display
searchOnChange: false,
// Optional, show a reset button per filter group, this will only display if searchOnChange is false
resetFilter: false,
// Optional, the label to use for the reset button above, this will only display if searchOnChange is false
resetFilterLabel: 'reset',
// Optional, show a reset-all button for the filter control. Defaults to displaying a reset button if searchOnChange is false.
resetFilters: true,
// Optional, the label to use for the reset-all button above, this will only display if resetFilters is true.
resetFiltersLabel: 'reset-all',
// Optional, allow collapsing excess filter options after a limit
showMore: true,
// Optional, the max number of filter to show before collapsing extras
showMoreLimit: 5,
// Optional, the label to show for displaying more filter
showMoreLabel: 'show more',
// Optional, the label to show for displaying less filter
showLessLabel: 'show less',
// Optional, allow expanding and collapsing entire groups of filters
expand: true,
// Optional, show the number of applied filter when a group is collapsed
showNumberApplied: true,
// Optional, the label to show on the apply button, this will only display if searchOnChange is false
applyLabel: 'apply',
// Optional, whether or not this filterbox contains dynamic filters, default false
isDynamic: true
});
```
## Facets Component
This component is only for Vertical pages.
The Facets component displays filters relevant to the current search, configured on the server, automatically. The Facets component will be hidden when a query returns no results. The selected options in a facets component will float to the top.
```html
```
```js
ANSWERS.addComponent('Facets', {
// Required, the selector for the container element where the component will be injected
container: '.facets-container',
// Optional, title to display above the facets
title: 'Filters',
// Optional, show number of results for each facet
showCount: true,
// Optional, execute a new search whenever a facet selection changes
searchOnChange: false,
// Optional, show a reset button per facet group
resetFacet: false,
// Optional, the label to use for the reset button above
resetFacetLabel: 'reset',
// Optional, show a reset-all button for the facets control. Defaults to showing a reset-all button if searchOnChange is false.
resetFacets: true,
// Optional, the label to use for the reset-all button above
resetFacetsLabel: 'reset-all',
// Optional, allow collapsing excess facet options after a limit
showMore: true,
// Optional, the max number of facets to show before collapsing extras
showMoreLimit: 5,
// Optional, the label to show for displaying more facets
showMoreLabel: 'show more',
// Optional, the label to show for displaying less facets
showLessLabel: 'show less',
// Optional, allow expanding and collapsing entire groups of facets
expand: true,
// Optional, show the number of applied facets when a group is collapsed
showNumberApplied: true,
// Optional, the placeholder text used for the filter option search input
placeholderText: 'Search here...',
// Optional, if true, display the filter option search input
searchable: false,
// Optional, the form label text for the search input, defaults to 'Search for a filter option'
searchLabelText: 'Search for a filter option',
// Optional, a transform function which is applied to an array of facets
// See the "Transforming Facets" section below for more info
transformFacets: (facets, config => facets),
// DEPRECATED, please use transformFacets instead. This option is disabled if transformFacets is supplied
// Optional, field-specific overrides for a filter
fields: {
'c_customFieldName': { // Field id to override e.g. c_customFieldName, builtin.location
// Optional, the placeholder text used for the filter option search input
placeholderText: 'Search here...',
// Optional, show a reset button per facet group
showReset: false,
// Optional, the label to use for the reset button above
resetLabel: 'reset',
// Optional, if true, display the filter option search input
searchable: false,
// Optional, the form label text for the search input, defaults to 'Search for a filter option'
searchLabelText: 'Search for a filter option',
// Optional, control type, singleoption or multioption
control: 'singleoption',
// Optional, override the field name for this facet
label: 'My custom field'
// Optional, allow collapsing excess facet options after a limit
showMore: true,
// Optional, the max number of facets to show before collapsing extras
showMoreLimit: 5,
// Optional, the label to show for displaying more facets
showMoreLabel: 'show more',
// Optional, the label to show for displaying less facets
showLessLabel: 'show less',
// Optional, allow expanding and collapsing entire groups of facets
expand: true,
// Optional, callback function for when a facet is changed
onChange: function() { console.log('Facet changed'); },
// Optional, the selector used for options in the template, defaults to '.js-yext-filter-option'
optionSelector: '.js-yext-filter-option',
}
},
// Optional, the label to show on the apply button
applyLabel: 'apply'
});
```
### Transforming Facets
The `transformFacets` option of the Facets component allows facets data to be fully customized. The function takes in and returns an array of the search-core DisplayableFacet which is described [here](https://github.com/yext/search-core/blob/master/docs/search-core.displayablefacet.md). The function also has access to the Facets config as the second parameter.
Here's an example of using this option to customize a boolean facet.
```js
transformFacets: facets => {
return facets.map(facet => {
const options = facet.options.map(option => {
let displayName = option.displayName;
if (facet.fieldId === 'c_acceptingNewPatients') {
if (option.value === false) { displayName = "Not Accepting Patients"; }
if (option.value === true) { displayName = "Accepting Patients"; }
}
return Object.assign({}, option, { displayName });
});
return Object.assign({}, facet, { options });
});
},
```
## FilterSearch Component
The FilterSearch component provides a text input box for users to type a query and select a preset matching filter. When a filter is selected, a vertical search is performed, and the filter and query are stored in the url. If multiple FilterSearch components are on the page, the search will include all selected filters across all of the components.
```html
```
```js
ANSWERS.addComponent('FilterSearch', {
// Required, the selector for the container element where the component will be injected
container: '.filter-search-container',
// Required
verticalKey: '',
// Required, the search parameters for autocompletion
searchParameters: {
// List of fields to query for
fields: [{
// Field id to query for e.g. c_customFieldName, builtin.location
fieldId: 'builtin.location',
// Entity type api name e.g. healthcareProfessional, location, ce_person
entityTypeId: 'ce_person',
}],
// Optional, if true, sections search results by search filter, default false
sectioned: false
},
// Optional, no default
placeholderText: 'Start typing...',
// Optional, if true, the selected filter is saved and used for the next search,
// but does not trigger a search itself. Defaults to false.
storeOnChange: true,
// Optional, defaults to native form node within container
formSelector: '.js-form',
// Optional, the input element used for searching and wires up the keyboard interaction
inputEl: '.js-query',
// Optional, provided to the template as a data point
title: 'title',
// Optional, the search text used for labeling the input box, also provided to template
searchText: 'What do you want to search',
// Optional, the query text to show as the first item for auto complete
promptHeader: 'Header',
// Optional, auto focuses the input box if set to true, default false
autoFocus: true,
// Optional, redirect search query to url
redirectUrl: 'path/to/url',
// Optional, the query displayed on load. Defaults to the query stored in the url (if any).
query: 'Green Ice Cream Flavor',
// Optional, the filter for filtersearch to apply on load, defaults to the filter stored in the url (if any).
// An example filter is shown below. For more information see the filter section of
// https://developer.yext.com/docs/api-reference/#operation/KnowledgeApiServer.listEntities
filter: {
c_iceCreamFlavors: {
$eq: 'pistachio'
}
}
})
```
## Filter Components
Filter components can be used in a FilterBox or on their own to affect a search.
### FilterOptions
FilterOptions displays a set of filters with either checkboxes or radio buttons.
As a user interacts with FilterOptions, information on which options are selected
is stored in the url. Returning to that same url will load the page with those saved
options already selected.
```html
```
```js
ANSWERS.addComponent('FilterOptions', {
// Required, the selector for the container element where the component will be injected
container: '.filter-container',
// Required, control type: 'singleoption' or 'multioption'
control: 'singleoption',
// The type of options to filter by, either 'STATIC_FILTER' or 'RADIUS_FILTER'.
// Defaults to 'STATIC_FILTER'.
optionType: 'STATIC_FILTER',
// Required, list of options
options: [
/** Depends on the above optionType, either 'STATIC_FILTER' or 'RADIUS_FILTER', see below. **/
],
// Optional, if true, the filter value is saved on change and sent with the next search. Defaults to true.
storeOnChange: true,
// Optional, the selector used for options in the template, defaults to '.js-yext-filter-option'
optionSelector: '.js-yext-filter-option',
// Optional, if true, show a reset button
showReset: false,
// Optional, the label to use for the reset button, defaults to 'reset'
resetLabel: 'reset',
// Optional, allow collapsing excess filter options after a limit, defaults to true
showMore: true,
// Optional, the max number of filter options to show before collapsing extras, defaults to 5
showMoreLimit: 5,
// Optional, the label to show for displaying more options, defaults to 'show more'
showMoreLabel: 'show more',
// Optional, the label to show for displaying less options, defaults to 'show less'
showLessLabel: 'show less',
// Optional, allow expanding and collapsing the filter, defaults to true
showExpand: true,
// Optional, show the number of applied options when a group is collapsed, defaults to true
showNumberApplied: true,
// Optional, the callback function to call when changed
onChange: function() {},
// Optional, the label to be used in the legend, defaults to 'Filters'
label: 'Filters',
// Optional, the placeholder text used for the filter option search input
placeholderText: 'Search here...',
// Optional, if true, display the filter option search input
searchable: false,
// Optional, the form label text for the search input, defaults to 'Search for a filter option'
searchLabelText: 'Search for a filter option',
});
```
The options config varies depending on whether the optionType is 'STATIC_FILTER' or 'RADIUS_FILTER'.
A STATIC_FILTER allows you to filter on a specified field, while a RADIUS_FILTER allows you to filter
results based on their distance from the user.
##### STATIC_FILTER
```js
{
options: [
{
// Required, the api field to filter on, configured on the Yext platform.
field: 'c_openNow',
// Required, the value for the above field to filter by.
value: true,
// Optional, the label to show next to the filter option.
label: 'Open Now',
// Optional, whether this option will be selected on page load. Selected options stored in the url
// take priority over this. Defaults to false.
selected: false
},
{
field: 'c_dogFriendly',
value: true,
label: 'Dog Friendly',
selected: true
},
{
field: 'c_storeType',
value: 'Megastore',
label: 'Megastores'
}
]
}
```
##### RADIUS_FILTER
```js
{
options: [
{
// Required, the value of the radius to apply (in meters). If this value is 0, the SDK will not add explicit radius filtering to the request. The backend may still perform its own filtering depending on the query given.
value: 8046.72,
// Optional, the label to show next to the filter option.
label: '5 miles',
// Optional, whether this option will be selected on page load. Selected options stored in the url
// take priority over this. Defaults to false.
selected: false
},
{
value: 16093.4,
label: '10 miles',
selected: true
},
{
value: 40233.6,
label: '25 miles'
},
{
value: 80467.2,
label: '50 miles'
},
{
value: 0,
label: "Do not filter by radius"
}
],
}
```
### RangeFilter
Displays two numeric inputs for selecting a number range.
```html
```
```js
ANSWERS.addComponent('RangeFilter', {
// Required, the selector for the container element where the component will be injected
container: '.range-filter-container',
// Required, the API name of the field to filter on
field: 'outdoorPoolCount',
// Optional, title to display for the range control, defaults to empty legend
title: 'Number of Outdoor Pools',
// Optional, the label to show next to the min value, defaults to no label
minLabel: 'At Least',
// Optional, the placeholder text for the min value, defaults to 'Min'
minPlaceholderText: 'Min',
// Optional, the label to show next to the max value, defaults to no label
maxLabel: 'Not More Than',
// Optional, the placeholder text for the max value, defaults to 'Max'
maxPlaceholderText: 'Max',
// Optional, the initial min value to show, defaults to 0. Set this to null to clear the value.
initialMin: 1,
// Optional, the initial max value to show, defaults to 10. Set this to null to clear the value.
initialMax: 5,
// Optional, the callback function to call when changed
onChange: function() {}
});
```
### DateRangeFilter
Displays two date inputs for selecting a range of dates.
```html
```
```js
ANSWERS.addComponent('DateRangeFilter', {
// Required, the selector for the container element where the component will be injected
container: '.date-range-filter-container',
// Required, the API name of the field to filter on
field: 'time.start',
// Optional, title to display for the range, defaults to empty legend
title: 'Event Start Date',
// Optional, the label to show next to the min date, defaults to no label
minLabel: 'Earliest',
// Optional, the label to show next to the max date, defaults to no label
maxLabel: 'Latest',
// Optional, the initial min date to show in yyyy-mm-dd format, defaults to today. Set this to null to clear the value.
initialMin: '2019-08-01',
// Optional, the initial max date to show in yyyy-mm-dd format, defaults to today. Set this to null to clear the value.
initialMax: '2019-09-01',
// Optional, whether to store the filter on change to input
storeOnChange: true,
// Optional, if true, this filter represents an exclusive range, rather than an inclusive one, defaults to false
isExclusive: false,
// Optional, the callback function to call when changed
onChange: function() {}
});
```
### GeoLocationFilter
Displays a "Use My Location" button that filters results to a radius around the user's current position.
```html
```
For all optional config in the example, unless otherwise specified, the default is the example value.
```js
ANSWERS.addComponent('GeoLocationFilter', {
// Required, the selector for the container element where the component will be injected
container: '.geolocation-filter-container',
// Optional, the vertical key to use
verticalKey: 'verticalKey',
// Optional, radius around the user, in miles, to find results, default 50
radius: 50,
// Optional, the text to show when enabled
enabledText: 'Disable My Location',
// Optional, the text to show ehn loading the user's location
loadingText: 'Loading',
// Optional, The label to show when unable to get the user's location
errorText: 'Unable To Use Location',
// Optional, CSS selector of the button
buttonSelector: '.js-yxt-GeoLocationFilter-button',
// Optional, Css selector of the query input
inputSelector: '.js-yxt-GeoLocationFilter-input',
// Optional, if true, triggers a search on each change to a filter, default false
searchOnChange: true,
// Optional, the icon url to show in the geo button
geoButtonIcon: 'path/to/url',
// Optional, the alt text to use with the geo button's icon
geoButtonIconAltText: 'Use My Location',
// Optional, the text to show in the geo button
geoButtonText: 'Use my location',
// Optional, Search parameters for the geolocation autocomplete
searchParameters: {
// List of fields to query for
fields: [{
// Field id to query for e.g. c_customFieldName, builtin.location
fieldId: 'builtin.location',
// Entity type api name e.g. healthcareProfessional, location, ce_person
entityTypeId: 'ce_person',
// Optional, if true sections search results by search filter, default false
sectioned: false,
}]
},
// Optional, options to pass to the geolocation api, which is used to fetch the user's current location.
// https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions
geolocationOptions: {
// Optional, whether to improve accuracy at the cost of response time and/or power consumption, defaults to false.
enableHighAccuracy: false,
// Optional, the maximum amount of time (in ms) a geolocation call is allowed to take before defaulting, defaults to 6 seconds.
timeout: 6000,
// Optional, the maximum amount of time (in ms) to cache a geolocation call, defaults to 5 minutes.
maximumAge: 300000,
},
// Optional, options for an alert when the geolocation call fails.
geolocationTimeoutAlert: {
// Optional, whether to display a window.alert() on the page, defaults to false.
enabled: false,
// Optional, the message in the alert. Defaults to the below
message: "We are unable to determine your location"
}
});
```
## Applied Filters Component
The Applied Filters Component displays your currently applied filters as a row of text tags, labeled
by filter display value. If the "removable" config option is set to true, these text tags will instead
be "removable filters", which, when clicked, will remove the clicked filter from the search.
Only intended for vertical pages.
```js
ANSWERS.addComponent('AppliedFilters', {
container: '.applied-filters-container',
// Optional, The vertical key of your search. Defaults to the vertical key specified in the search config.
verticalKey: 'aVerticalKey',
// Optional, Whether to display the field name of each group of applied filters. e.g. "Location: Virginia, New York" vs just "Virginia, New York". Defaults to false.
showFieldNames: false,
// Optional, This is list of filters that should not be displayed. Defaults to hiding ['builtin.entityType'].
hiddenFields: ['builtin.entityType'],
// Optional, Whether or not the displayed filters should be removable filters, or just simple text tags. Defaults to false (text tags).
removable: false,
// Optional, The character that separates each group of filters (grouped by field name). Defaults to '|'.
delimiter: '|',
// Optional, The aria-label given to the component. Defaults to 'Filters applied to this search:'.
labelText: 'Filters applied to this search:',
// Optional, The aria-label given to the removable filters. Defaults to 'Remove this filter'.
removableLabelText: 'Remove this filter'
});
```
## Navigation Component
The Navigation Component adds a dynamic experience to your pages navigation experience.
When using multiple vertical searches in a universal search, the navigation ordering will be automatically updated based on the search results. By default, tabs that do not fit in the container will go inside a dropdown menu.
Vertical configurations should be provided the ANSWERS.init's `verticalPages` configuration. Find more info in the [Vertical Pages Configuration](#vertical-pages-configuration) section.
```html