UNPKG

coveo-search-ui

Version:

Coveo JavaScript Search Framework

193 lines (169 loc) 7.66 kB
import 'styling/_Breadcrumb'; import { each, find, isEmpty } from 'underscore'; import { BreadcrumbEvents, IBreadcrumbItem, IClearBreadcrumbEventArgs, IPopulateBreadcrumbEventArgs } from '../../events/BreadcrumbEvents'; import { InitializationEvents } from '../../events/InitializationEvents'; import { QueryEvents } from '../../events/QueryEvents'; import { exportGlobally } from '../../GlobalExports'; import { l } from '../../strings/Strings'; import { AccessibleButton } from '../../utils/AccessibleButton'; import { $$ } from '../../utils/Dom'; import { analyticsActionCauseList, IAnalyticsNoMeta } from '../Analytics/AnalyticsActionListMeta'; import { Component } from '../Base/Component'; import { IComponentBindings } from '../Base/ComponentBindings'; import { ComponentOptions } from '../Base/ComponentOptions'; import { Initialization } from '../Base/Initialization'; export interface IBreadcrumbOptions { headingLevel?: number; } /** * The Breadcrumb component displays a summary of the currently active query filters. * * For example, when the user selects a {@link Facet} value, the breadcrumbs display this value. * * The active filters are obtained by the component by firing an event in the Breadcrumb component. * * All other components having an active state can react to this event by providing custom bits of HTML to display * inside the breadcrumbs. * * Thus, it is possible to easily extend the Breadcrumb component using custom code to display information about custom * states and filters. * * See {@link BreadcrumbEvents} for the list of events and parameters sent when a Breadcrumb component is populated. */ export class Breadcrumb extends Component { static ID = 'Breadcrumb'; /** * The options for the Breadcrumb. * @componentOptions */ static options: IBreadcrumbOptions = { /** * The [heading level](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements) to use for the heading before the breadcrumbs. * * A value of 0 will render a [`div`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div) element instead. */ headingLevel: ComponentOptions.buildNumberOption({ min: 0, max: 6 }) }; static doExport = () => { exportGlobally({ Breadcrumb: Breadcrumb }); }; private lastBreadcrumbs: IBreadcrumbItem[]; /** * Creates a new Breadcrumb component. Binds event on {@link QueryEvents.deferredQuerySuccess} to draw the * breadcrumbs. * @param element The HTMLElement on which to instantiate the component. * @param options The options for the Breadcrumb component. * @param bindings The bindings that the component requires to function normally. If not set, these will be * automatically resolved (with a slower execution time). */ constructor(public element: HTMLElement, public options?: IBreadcrumbOptions, bindings?: IComponentBindings) { super(element, Breadcrumb.ID, bindings); this.options = ComponentOptions.initComponentOptions(element, Breadcrumb, options); this.bind.oneRootElement(InitializationEvents.afterInitialization, () => this.handleAfterInitialization()); this.bind.onRootElement(BreadcrumbEvents.redrawBreadcrumb, () => this.redrawBreadcrumb()); this.element.style.display = 'none'; this.element.setAttribute('tabindex', '-1'); this.addDefaultAccessibilityAttributes(); } /** * Triggers the event to populate the breadcrumbs. Components such as {@link Facet} can populate the breadcrumbs. * * This method triggers a {@link BreadcrumbEvents.populateBreadcrumb} event with an * {@link IPopulateBreadcrumbEventArgs} object (an array) that other components or code can populate. * @returns {IBreadcrumbItem[]} A populated breadcrumb item list. */ public getBreadcrumbs(): IBreadcrumbItem[] { const args = <IPopulateBreadcrumbEventArgs>{ breadcrumbs: [], headingLevel: this.options.headingLevel }; this.bind.trigger(this.root, BreadcrumbEvents.populateBreadcrumb, args); this.logger.debug('Retrieved breadcrumbs', args.breadcrumbs); // If newly received breadcrumbs are empty, and last breadcrumbs were not. // this means end user removed the last filter: // We want to shift keyboard focus to the result list container in that case, for ease of use of the interface // https://coveord.atlassian.net/browse/JSUI-3078 if (isEmpty(args.breadcrumbs) && !isEmpty(this.lastBreadcrumbs)) { this.focusFirstEnabledResultList(); } this.lastBreadcrumbs = args.breadcrumbs; return args.breadcrumbs; } /** * Triggers the event to clear the current breadcrumbs that components such as {@link Facet} can populate. * * Also triggers a new query and logs the `breadcrumbResetAll` event in the usage analytics. */ public clearBreadcrumbs() { const args = <IClearBreadcrumbEventArgs>{}; this.bind.trigger(this.root, BreadcrumbEvents.clearBreadcrumb, args); this.logger.debug('Clearing breadcrumbs'); this.usageAnalytics.logSearchEvent<IAnalyticsNoMeta>(analyticsActionCauseList.breadcrumbResetAll, {}); this.queryController.executeQuery(); } /** * Draws the specified breadcrumb items. * @param breadcrumbs The breadcrumb items to draw. */ public drawBreadcrumb(breadcrumbs: IBreadcrumbItem[]) { $$(this.element).empty(); if (breadcrumbs.length != 0) { this.element.style.display = ''; } else { this.element.style.display = 'none'; } const breadcrumbItems = document.createElement('div'); $$(breadcrumbItems).addClass('coveo-breadcrumb-items'); this.element.appendChild(breadcrumbItems); each(breadcrumbs, (bcrumb: IBreadcrumbItem) => { const elem = bcrumb.element; $$(elem).addClass('coveo-breadcrumb-item'); breadcrumbItems.appendChild(elem); }); const clearText = $$('div', undefined, l('ClearAllFilters')).el; const clear = $$( 'div', { className: 'coveo-breadcrumb-clear-all' }, clearText ).el; new AccessibleButton() .withElement(clear) .withSelectAction(() => this.clearBreadcrumbs()) .withOwner(this.bind) .withoutLabelOrTitle() .build(); this.element.appendChild(clear); } private redrawBreadcrumb() { this.lastBreadcrumbs ? this.drawBreadcrumb(this.lastBreadcrumbs) : this.drawBreadcrumb(this.getBreadcrumbs()); } private handleDeferredQuerySuccess() { this.drawBreadcrumb(this.getBreadcrumbs()); } private handleQueryError() { this.drawBreadcrumb(this.getBreadcrumbs()); } private handleAfterInitialization() { // We must bind to these events after the initialization to make sure the breadcrumb generation // is made with updated components. (E.G facet, facetrange, ...) this.bind.onRootElement(QueryEvents.deferredQuerySuccess, () => this.handleDeferredQuerySuccess()); this.bind.onRootElement(QueryEvents.queryError, () => this.handleQueryError()); } private focusFirstEnabledResultList() { const resultLists = this.searchInterface.getComponents<Component>('ResultList'); const firstEnabledResultList = find(resultLists, resultList => resultList.disabled === false); if (firstEnabledResultList) { $$(firstEnabledResultList.element).focus(true); } } private addDefaultAccessibilityAttributes() { if (!this.element.getAttribute('role')) { this.element.setAttribute('role', 'navigation'); } if (!this.element.getAttribute('aria-label')) { this.element.setAttribute('aria-label', l('Breadcrumb')); } } } Initialization.registerAutoCreateComponent(Breadcrumb);