coveo-search-ui
Version:
Coveo JavaScript Search Framework
289 lines (243 loc) • 11 kB
text/typescript
import { CategoryFacet } from './CategoryFacet';
import { FacetSearchElement } from '../Facet/FacetSearchElement';
import { pluck, debounce, last } from 'underscore';
import { $$, Dom } from '../../utils/Dom';
import { SVGDom } from '../../utils/SVGDom';
import { SVGIcons } from '../../utils/SVGIcons';
import { l } from '../../strings/Strings';
import { IGroupByValue } from '../../rest/GroupByValue';
import 'styling/_CategoryFacetSearch';
import { StringUtils } from '../../utils/StringUtils';
import { analyticsActionCauseList, IAnalyticsCategoryFacetMeta } from '../Analytics/AnalyticsActionListMeta';
import { IFacetSearch } from '../Facet/IFacetSearch';
import { IIndexFieldValue } from '../../rest/FieldValue';
import { AccessibleButton } from '../../utils/AccessibleButton';
import * as Globalize from 'globalize';
export class CategoryFacetSearch implements IFacetSearch {
public container: Dom | undefined;
public facetSearchElement: FacetSearchElement;
public displayNewValues: () => void;
public currentlyDisplayedResults: string[];
public moreValuesToFetch = true;
public facetSearchPromise: Promise<IIndexFieldValue[]>;
private numberOfValuesToFetch: number;
constructor(private categoryFacet: CategoryFacet, private displayButton = true) {
this.facetSearchElement = new FacetSearchElement(this);
this.displayNewValues = debounce(this.getDisplayNewValuesFunction(), this.categoryFacet.options.facetSearchDelay);
this.categoryFacet.root.addEventListener('click', (e: MouseEvent) => this.handleClickElsewhere(e));
this.numberOfValuesToFetch = this.categoryFacet.options.numberOfResultsInFacetSearch;
}
public get facetType() {
return CategoryFacet.ID;
}
public get facetTitle() {
return this.categoryFacet.options.title || this.categoryFacet.options.field.toString();
}
public setExpandedFacetSearchAccessibilityAttributes(searchResultsElements: HTMLElement) {
this.container.setAttribute('aria-expanded', 'true');
}
public setCollapsedFacetSearchAccessibilityAttributes() {
this.container.setAttribute('aria-expanded', 'false');
}
public build() {
this.container = $$('div', {
className: 'coveo-category-facet-search-container',
role: 'button'
});
this.container.toggleClass('coveo-category-facet-search-without-button', !this.displayButton);
this.displayButton && this.buildButton();
this.container.append(this.facetSearchElement.build());
$$(this.facetSearchElement.search).toggleClass('without-animation', !this.displayButton);
return this.container;
}
private buildButton() {
new AccessibleButton()
.withElement(this.container)
.withSelectAction(() => {
$$(this.categoryFacet.element).addClass('coveo-category-facet-searching');
this.focus();
})
.withLabel(l('SearchFacetResults', this.facetTitle))
.build();
this.buildfacetSearchPlaceholder();
}
public focus() {
this.facetSearchElement.focus();
}
public clear() {
this.dismissSearchResults();
this.container && this.container.detach();
}
public dismissSearchResults() {
this.removeNoResultsCssClasses();
$$(this.categoryFacet.element).removeClass('coveo-category-facet-searching');
$$(this.facetSearchElement.searchResults).empty();
this.facetSearchElement.clearSearchInput();
this.facetSearchElement.hideSearchResultsElement();
this.currentlyDisplayedResults = null;
this.numberOfValuesToFetch = this.categoryFacet.options.numberOfResultsInFacetSearch;
this.moreValuesToFetch = true;
}
public keyboardEventDefaultHandler() {
this.moreValuesToFetch = true;
this.displayNewValues();
}
public keyboardNavigationEnterPressed() {
this.selectCurrentResult();
}
public fetchMoreValues() {
this.numberOfValuesToFetch += this.categoryFacet.options.numberOfResultsInFacetSearch;
this.displayNewValues();
}
public getCaptions() {
const searchResults = $$(this.facetSearchElement.searchResults);
const captions = searchResults
.findAll('.coveo-category-facet-search-value-caption')
.concat(searchResults.findAll('.coveo-category-facet-search-path-parents'))
.concat(searchResults.findAll('.coveo-category-facet-search-path-last-value'));
return captions;
}
public updateAriaLive(text: string) {
this.categoryFacet.searchInterface.ariaLive.updateText(text);
}
private selectCurrentResult() {
if (this.facetSearchElement.currentResult) {
const currentResultPathData = this.facetSearchElement.currentResult.el.dataset.path;
const delimiter = this.categoryFacet.options.delimitingCharacter;
const path = currentResultPathData.split(delimiter);
this.categoryFacet.changeActivePath(path);
this.categoryFacet.logAnalyticsEvent(analyticsActionCauseList.categoryFacetSelect, path);
this.categoryFacet.executeQuery();
this.categoryFacet.scrollToTop();
}
}
private handleClickElsewhere(e: MouseEvent) {
const closestContainer = $$(<HTMLElement>e.target).closest('.coveo-category-facet-search-container');
const isSelfContainer = this.container && closestContainer === this.container.el;
if (!closestContainer || !isSelfContainer) {
this.dismissSearchResults();
}
}
private buildfacetSearchPlaceholder() {
const placeholder = $$('div', { className: 'coveo-category-facet-search-placeholder' });
const icon = $$('div', { className: 'coveo-category-facet-search-icon' }, SVGIcons.icons.checkboxHookExclusionMore);
SVGDom.addClassToSVGInContainer(icon.el, 'coveo-category-facet-search-icon-svg');
const label = $$('span', { className: 'coveo-category-facet-search-label' }, l('Search'));
placeholder.append(icon.el);
placeholder.append(label.el);
this.container.append(placeholder.el);
}
private getDisplayNewValuesFunction() {
return async () => {
this.facetSearchElement.showFacetSearchWaitingAnimation();
this.categoryFacet.logger.info('Triggering new Category Facet search');
const categoryFacetValues = await this.categoryFacet.categoryFacetQueryController.searchFacetValues(
this.facetSearchElement.input.value,
this.numberOfValuesToFetch
);
this.logAnalyticsEvent();
if (categoryFacetValues.length < this.numberOfValuesToFetch) {
this.moreValuesToFetch = false;
}
if (categoryFacetValues.length == 0) {
this.noFacetSearchResults();
return;
}
this.removeNoResultsCssClasses();
this.setFacetSearchResults(categoryFacetValues);
if (this.shouldPositionSearchResults) {
this.facetSearchElement.positionSearchResults();
}
this.facetSearchElement.hideFacetSearchWaitingAnimation();
};
}
private setFacetSearchResults(categoryFacetValues: IGroupByValue[]) {
$$(this.facetSearchElement.searchResults).empty();
this.currentlyDisplayedResults = pluck(categoryFacetValues, 'value');
for (let i = 0; i < categoryFacetValues.length; i++) {
const searchResult = this.buildFacetSearchValue(categoryFacetValues[i], i);
if (i == 0) {
this.facetSearchElement.setAsCurrentResult(searchResult);
}
this.facetSearchElement.appendToSearchResults(searchResult.el);
}
this.highlightCurrentQueryWithinSearchResults();
this.facetSearchElement.updateAriaLiveWithResults(
this.facetSearchElement.input.value,
this.currentlyDisplayedResults.length,
this.moreValuesToFetch
);
}
private getFormattedCount(count: number) {
return Globalize.format(count, 'n0');
}
private buildFacetSearchValue(categoryFacetValue: IGroupByValue, index: number) {
const path = categoryFacetValue.value.split(this.categoryFacet.options.delimitingCharacter);
const pathParents = path.slice(0, -1).length != 0 ? `${path.slice(0, -1).join('/')}/` : '';
const value = $$('span', { className: 'coveo-category-facet-search-value-caption' }, last(path));
const number = $$(
'span',
{ className: 'coveo-category-facet-search-value-number' },
this.getFormattedCount(categoryFacetValue.numberOfResults)
);
const pathParentsCaption = $$('span', { className: 'coveo-category-facet-search-path-parents' }, pathParents);
const pathToValueCaption = $$('span', { className: 'coveo-category-facet-search-path' }, pathParentsCaption);
const firstRow = $$('div', { className: 'coveo-category-facet-search-first-row' }, value, number);
const secondRow = $$('div', { className: 'coveo-category-facet-search-second-row' }, pathToValueCaption);
const item = $$(
'li',
{
id: `coveo-category-facet-search-suggestion-${index}`,
role: 'option',
ariaSelected: 'false',
className: 'coveo-category-facet-search-value',
title: path
},
firstRow,
secondRow
);
item.el.dataset.path = categoryFacetValue.value;
const countLabel = l('ResultCount', this.getFormattedCount(categoryFacetValue.numberOfResults), categoryFacetValue.numberOfResults);
const label = l('IncludeValueWithResultCount', last(path), countLabel);
new AccessibleButton()
.withElement(item)
.withSelectAction(() => {
this.categoryFacet.changeActivePath(path);
this.categoryFacet.scrollToTop();
this.categoryFacet.logAnalyticsEvent(analyticsActionCauseList.categoryFacetSelect, path);
this.categoryFacet.executeQuery();
})
.withLabel(label)
.build();
return item;
}
private noFacetSearchResults() {
this.facetSearchElement.hideFacetSearchWaitingAnimation();
this.facetSearchElement.emptyAndShowNoResults();
$$(this.facetSearchElement.search).addClass('coveo-facet-search-no-results');
$$(this.categoryFacet.element).addClass('coveo-no-results');
}
private removeNoResultsCssClasses() {
this.facetSearchElement.search && $$(this.facetSearchElement.search).removeClass('coveo-facet-search-no-results');
$$(this.categoryFacet.element).removeClass('coveo-no-results');
}
private highlightCurrentQueryWithinSearchResults() {
const regex = new RegExp(`(${StringUtils.stringToRegex(this.facetSearchElement.input.value, true)})`, 'ig');
this.facetSearchElement.highlightCurrentQueryInSearchResults(regex);
}
private logAnalyticsEvent() {
this.categoryFacet.usageAnalytics.logCustomEvent<IAnalyticsCategoryFacetMeta>(
analyticsActionCauseList.categoryFacetSearch,
{
categoryFacetId: this.categoryFacet.options.id,
categoryFacetField: this.categoryFacet.options.field.toString(),
categoryFacetTitle: this.categoryFacet.options.title
},
this.categoryFacet.root
);
}
private get shouldPositionSearchResults() {
const searchResults = this.facetSearchElement.searchResults;
return searchResults && !searchResults.parentElement;
}
}