UNPKG

coveo-search-ui

Version:

Coveo JavaScript Search Framework

903 lines (792 loc) • 32.6 kB
import 'styling/DynamicFacet/_DynamicFacet'; import { IDynamicFacet, IDynamicFacetOptions } from './IDynamicFacet'; import { defer, difference, findIndex } from 'underscore'; import { $$ } from '../../utils/Dom'; import { exportGlobally } from '../../GlobalExports'; import { Component } from '../Base/Component'; import { IComponentBindings } from '../Base/ComponentBindings'; import { ComponentOptions } from '../Base/ComponentOptions'; import { Initialization } from '../Base/Initialization'; import { ResponsiveFacetOptions } from '../ResponsiveComponents/ResponsiveFacetOptions'; import { ResponsiveDynamicFacets } from '../ResponsiveComponents/ResponsiveDynamicFacets'; import { DynamicFacetBreadcrumbs } from './DynamicFacetBreadcrumbs'; import { DynamicFacetHeader } from './DynamicFacetHeader/DynamicFacetHeader'; import { DynamicFacetValues } from './DynamicFacetValues/DynamicFacetValues'; import { QueryEvents, IQuerySuccessEventArgs, IDoneBuildingQueryEventArgs } from '../../events/QueryEvents'; import { QueryStateModel } from '../../models/QueryStateModel'; import { DynamicFacetQueryController } from '../../controllers/DynamicFacetQueryController'; import { Utils } from '../../utils/Utils'; import { MODEL_EVENTS, IAttributesChangedEventArg } from '../../models/Model'; import { Assert } from '../../misc/Assert'; import { IFacetResponse } from '../../rest/Facet/FacetResponse'; import { IStringMap } from '../../rest/GenericParam'; import { isFacetSortCriteria, FacetSortCriteria } from '../../rest/Facet/FacetSortCriteria'; import { l } from '../../strings/Strings'; import { DeviceUtils } from '../../utils/DeviceUtils'; import { BreadcrumbEvents, IPopulateBreadcrumbEventArgs } from '../../events/BreadcrumbEvents'; import { IAnalyticsActionCause, analyticsActionCauseList, IAnalyticsFacetMeta } from '../Analytics/AnalyticsActionListMeta'; import { IAnalyticsFacetState } from '../Analytics/IAnalyticsFacetState'; import { IQueryOptions } from '../../controllers/QueryController'; import { DynamicFacetManager } from '../DynamicFacetManager/DynamicFacetManager'; import { QueryBuilder } from '../Base/QueryBuilder'; import { DynamicFacetSearch } from '../DynamicFacetSearch/DynamicFacetSearch'; import { ResultListUtils } from '../../utils/ResultListUtils'; import { IQueryResults } from '../../rest/QueryResults'; import { FacetType } from '../../rest/Facet/FacetRequest'; import { DependsOnManager, IDependentFacet, IDependentFacetCondition } from '../../utils/DependsOnManager'; import { DynamicFacetValueCreator } from './DynamicFacetValues/DynamicFacetValueCreator'; import { Logger } from '../../misc/Logger'; import { FacetUtils } from '../Facet/FacetUtils'; /** * The `DynamicFacet` component displays a *facet* of the results for the current query. A facet is a list of values for a * certain field occurring in the results, ordered using a configurable criteria (e.g., number of occurrences). * * The list of values is obtained using an array of [`FacetRequest`]{@link IFacetRequest} operations performed at the same time * as the main query. * * The `DynamicFacet` component allows the end-user to drill down inside a result set by restricting the result to certain * field values. * * This facet is more easy to use than the original [`Facet`]{@link Facet} component. It implements additional Coveo Machine Learning (Coveo ML) features * such as dynamic navigation experience (DNE). * * @notSupportedIn salesforcefree * @availablesince [May 2019 Release (v2.6063)](https://docs.coveo.com/en/2909/) */ export class DynamicFacet extends Component implements IDynamicFacet { static ID = 'DynamicFacet'; static doExport = () => exportGlobally({ DynamicFacet }); /** * The options for the DynamicFacet * @componentOptions */ static options: IDynamicFacetOptions = { ...ResponsiveFacetOptions, /** * The unique identifier for this facet. * * Among other things, this is used to record and read the facet * state in the URL fragment identifier (see the * [`enableHistory`]{@link SearchInterface.options.enableHistory} `SearchInterface` * option). * * **Tip:** When several facets in a given search interface are based on * the same field, ensure that each of those facets has a distinct `id`. * * If specified, must contain between 1 and 60 characters. * Only alphanumeric (A-Za-z0-9), underscore (_), and hyphen (-) characters are kept; other characters are automatically removed. * * Defaults to the [`field`]{@link DynamicFacet.options.field} option value. * * @examples author-facet */ id: ComponentOptions.buildStringOption({ postProcessing: (value = '', options: IDynamicFacetOptions) => { const maxCharLength = 60; const sanitizedValue = value.replace(/[^A-Za-z0-9-_@]+/g, ''); if (Utils.isNonEmptyString(sanitizedValue)) { return sanitizedValue.slice(0, maxCharLength - 1); } return options.field.slice(0, maxCharLength - 1); }, section: 'CommonOptions' }), /** * The title to display for this facet. * * Defaults to the localized string for `NoTitle`. * * @examples Author */ title: ComponentOptions.buildLocalizedStringOption({ localizedString: () => l('NoTitle'), section: 'CommonOptions', priority: 10 }), /** * The name of the field on which to base this facet. * * The field must of the `string` type. * * Must be prefixed by `@`, and must reference an existing field whose * **Facet** option is enabled. * * @externaldocs [Add or Edit Fields](https://docs.coveo.com/en/1982/) * @examples @author */ field: ComponentOptions.buildFieldOption({ required: true, section: 'CommonOptions' }), /** * The sort criterion to use for this facet. * * See [`FacetSortCriteria`]{@link FacetSortCriteria} for the list and * description of allowed values. * * By default, the following behavior applies: * * - If the requested [`numberOfValues`]{@link DynamicFacet.options.numberOfValues} * is greater than or equal to the currently displayed number of values, * the [`alphanumeric`]{@link FacetSortCriteria.alphanumeric} criterion is * used. * - If the requested `numberOfValues` is less than the currently displayed * number of values and the facet is not currently expanded, the [`score`]{@link FacetSortCriteria.score} * criterion is used. * - Otherwise, the `alphanumeric` criterion is used. * * @examples score */ sortCriteria: <FacetSortCriteria>ComponentOptions.buildStringOption({ postProcessing: value => { if (!value) { return undefined; } if (isFacetSortCriteria(value)) { return value; } new Logger(value).warn('sortCriteria is not of the the allowed values: "score", "alphanumeric", "occurrences"'); return undefined; }, section: 'Sorting' }), /** * Specifies a custom order by which to sort the dynamic facet values. * * Custom-ordered values won't necessarily retrieve values from the index. * * **Example:** * * You could use this option to specify a logical order for support tickets, such as: * ```html * <div class="CoveoDynamicFacet" data-field="@ticketstatus" data-title="Ticket Status" data-tab="All" data-custom-sort="New,Opened,Feedback,Resolved"></div> * ``` * * **Note:** * > The [`DynamicFacetRange`]{@link DynamicFacetRange} component does not support this option. */ customSort: ComponentOptions.buildListOption<string>({ section: 'Sorting' }), /** * The number of values to request for this facet. * * Also determines the default maximum number of additional values to request each time this facet is expanded, * and the maximum number of values to display when this facet is collapsed (see the [`enableCollapse`]{@link DynamicFacet.options.enableCollapse} option). */ numberOfValues: ComponentOptions.buildNumberOption({ min: 0, defaultValue: 8, section: 'CommonOptions' }), /** * Whether to allow the end-user to expand and collapse this facet. */ enableCollapse: ComponentOptions.buildBooleanOption({ defaultValue: true, section: 'CommonOptions' }), /** * Whether to scroll back to the top of the page whenever the end-user interacts with the facet. */ enableScrollToTop: ComponentOptions.buildBooleanOption({ defaultValue: true, section: 'CommonOptions' }), /** * Whether to enable the **Show more** and **Show less** buttons in the facet. * * **Note:** The [`DynamicFacetRange`]{@link DynamicFacetRange} component does not support this option. */ enableMoreLess: ComponentOptions.buildBooleanOption({ defaultValue: true, section: 'CommonOptions' }), /** * Whether to allow the end-user to search the facet values. * * **Note:** The [`DynamicFacetRange`]{@link DynamicFacetRange} component does not support this option. * * By default, the following behavior applies: * * - Enabled when more facet values are available. * - Disabled when all available facet values are already displayed. */ enableFacetSearch: ComponentOptions.buildBooleanOption({ section: 'Filtering' }), /** * Whether to prepend facet search queries with a wildcard. * * **Note:** The [`DynamicFacetRange`]{@link DynamicFacetRange} component does not support this option. */ useLeadingWildcardInFacetSearch: ComponentOptions.buildBooleanOption({ defaultValue: true, section: 'Filtering', depend: 'enableFacetSearch' }), /** * Whether this facet should be collapsed by default. */ collapsedByDefault: ComponentOptions.buildBooleanOption({ defaultValue: false, section: 'CommonOptions', depend: 'enableCollapse' }), /** * Whether to notify the [`Breadcrumb`]{@link Breadcrumb} component when toggling values in the facet. * * See also the [`numberOfValuesInBreadcrumb`]{@link DynamicFacet.options.numberOfValuesInBreadcrumb} option. */ includeInBreadcrumb: ComponentOptions.buildBooleanOption({ defaultValue: true, section: 'CommonOptions' }), /** * The maximum number of selected values the [`Breadcrumb`]{@link Breadcrumb} component can display before outputting a **N more...** link for the facet. */ numberOfValuesInBreadcrumb: ComponentOptions.buildNumberOption({ defaultFunction: () => (DeviceUtils.isMobileDevice() ? 3 : 5), min: 0, depend: 'includeInBreadcrumb', section: 'CommonOptions' }), /** * A mapping of facet values to their desired captions. * * **Note:** The [`DynamicFacetRange`]{@link DynamicFacetRange} component does not support this option. * * @externaldocs [Normalizing Facet Value Captions](https://docs.coveo.com/368/). * @examples { "smith_alice": "Alice Smith"\, "jones_bob_r": "Bob R. Jones" } */ valueCaption: ComponentOptions.buildJsonOption<IStringMap<string>>({ defaultValue: {} }), /** * The [`id`]{@link DynamicFacet.options.id} of another facet in which at least one value must be selected in order for the dependent facet to be visible. * * By default, the facet does not depend on any other facet to be displayed. * * @examples document-type-facet * * @availablesince [December 2019 Release (v2.7610)](https://docs.coveo.com/en/3142/) */ dependsOn: ComponentOptions.buildStringOption({ section: 'CommonOptions' }), /** * A function that verifies whether the current state of the `dependsOn` facet allows the dependent facet to be displayed. * * If specified, the function receives a reference to the resolved `dependsOn` facet component instance as an argument, and must return a boolean. * The function's argument should typically be treated as read-only. * * By default, the dependent facet is displayed whenever one or more values are selected in its `dependsOn` facet. * * @externaldocs [Defining Dependent Facets](https://docs.coveo.com/3210/) * * @availablesince [April 2020 Release (v2.8864)](https://docs.coveo.com/en/3231/) */ dependsOnCondition: ComponentOptions.buildCustomOption<IDependentFacetCondition>( () => { return null; }, { depend: 'dependsOn', section: 'CommonOptions' } ), /** * The number of items to scan for facet values. * * Setting this option to a higher value may enhance the accuracy of facet value counts at the cost of slower query performance. * * @availablesince [January 2020 Release (v2.7968)](https://docs.coveo.com/en/3163/) * * @examples 1500 */ injectionDepth: ComponentOptions.buildNumberOption({ defaultValue: 1000, min: 1000 }), /** * Whether to exclude folded result parents when estimating result counts for facet values. * * **Note:** The target folding field must be a facet field with the **Use cache for nested queries** options enabled (see [Add or Edit a Field](https://docs.coveo.com/en/1982)). * * See also the [`Folding`]{@link Folding} and [`FoldingForThread`]{@link FoldingForThread} components. * * **Default:** `true`. * * @availablesince [March 2020 Release (v2.8521)](https://docs.coveo.com/en/3203/) */ filterFacetCount: ComponentOptions.buildBooleanOption({ section: 'Filtering' }), /** * The [heading level](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements) to use for the heading above the facet. * * A value of 0 will render a [`div`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div) element instead. * * **Default:** `2`. */ headingLevel: ComponentOptions.buildNumberOption({ defaultValue: 2, min: 0, max: 6 }) }; private includedAttributeId: string; private listenToQueryStateChange = true; private search: DynamicFacetSearch; private valueToFocusOnRender: string | null; public header: DynamicFacetHeader; public options: IDynamicFacetOptions; public dynamicFacetManager: DynamicFacetManager; public dependsOnManager: DependsOnManager; public dynamicFacetQueryController: DynamicFacetQueryController; public values: DynamicFacetValues; public position: number; public moreValuesAvailable = false; public isCollapsed: boolean; public isDynamicFacet = true; public isFieldValueCompatible = true; public isFieldValueHierarchical = false; /** * Creates a new `DynamicFacet` instance. * * @param element The element from which to instantiate the component. * @param options The component options. * @param bindings The component bindings. Automatically resolved by default. */ constructor( public element: HTMLElement, options?: IDynamicFacetOptions, bindings?: IComponentBindings, classId: string = DynamicFacet.ID ) { super(element, classId, bindings); this.options = ComponentOptions.initComponentOptions(element, DynamicFacet, options); this.initDynamicFacetQueryController(); this.initDependsOnManager(); this.initQueryEvents(); this.initQueryStateEvents(); this.initBreadCrumbEvents(); this.initComponentStateEvents(); this.initValues(); this.verifyCollapsingConfiguration(); this.isCollapsed = this.options.enableCollapse && this.options.collapsedByDefault; ResponsiveDynamicFacets.init(this.root, this, this.options); } public get fieldName() { return this.options.field.slice(1); } public get facetType() { return FacetType.specific; } /** * Selects a single value in this facet. * * Does **not** trigger a query automatically. * Does **not** update the visual of the facet until a query is performed. * * @param value The name of the facet value to select. */ public selectValue(value: string) { Assert.exists(value); this.selectMultipleValues([value]); } /** * Selects multiple values in this facet. * * Does **not** trigger a query automatically. * Does **not** update the visual of the facet until a query is performed. * * @param values The names of the facet values to select. */ public selectMultipleValues(values: string[]) { Assert.exists(values); this.ensureDom(); this.logger.info('Selecting facet value(s)', values); values.forEach(value => { this.values.get(value).select(); }); this.updateQueryStateModel(); } /** * Deselects a single value in this facet. * * Does **not** trigger a query automatically. * Does **not** update the visual of the facet until a query is performed. * * @param values The name of the facet value to deselect. */ public deselectValue(value: string) { Assert.exists(value); this.deselectMultipleValues([value]); } /** * Determines whether the specified value is selected in the facet. * @param value The name of the facet value to verify. */ public hasSelectedValue(value: string) { return this.values.hasSelectedValue(value); } /** * Deselects multiple values in this facet. * * Does **not** trigger a query automatically. * Does **not** update the visual of the facet until a query is performed. * * @param values The names of the facet values to deselect. */ public deselectMultipleValues(values: string[]) { Assert.exists(values); this.ensureDom(); this.logger.info('Deselecting facet value(s)', values); values.forEach(value => { this.values.get(value).deselect(); }); this.updateQueryStateModel(); } /** * Toggles the selection state of a single value in this facet. * * Does **not** trigger a query automatically. * * @param values The name of the facet value to toggle. */ public toggleSelectValue(value: string) { Assert.exists(value); this.ensureDom(); const facetValue = this.values.get(value); facetValue.toggleSelect(); this.logger.info('Toggle select facet value', facetValue); this.updateQueryStateModel(); } /** * Keyboard focuses a value once it has been re-rendered. * * @param value The name of the facet value to focus */ public focusValueAfterRerender(value: string) { Assert.exists(value); this.valueToFocusOnRender = value; } /** * Returns the configured caption for a desired facet value. * * @param value The string facet value whose caption the method should return. */ public getCaptionForStringValue(value: string) { return FacetUtils.getDisplayValueFromValueCaption(value, this.options.field as string, this.options.valueCaption); } /** * Requests additional values. * * Automatically triggers an isolated query. * @param additionalNumberOfValues The number of additional values to request. Minimum value is 1. Defaults to the [numberOfValues]{@link DynamicFacet.options.numberOfValues} option value. */ public showMoreValues(additionalNumberOfValues = this.options.numberOfValues) { this.ensureDom(); this.logger.info('Show more values'); this.dynamicFacetQueryController.increaseNumberOfValuesToRequest(additionalNumberOfValues); this.triggerNewIsolatedQuery(() => this.logAnalyticsFacetShowMoreLess(analyticsActionCauseList.dynamicFacetShowMore)); } /** * Reduces the number of displayed facet values down to [numberOfValues]{@link DynamicFacet.options.numberOfValues}. * * Automatically triggers an isolated query. */ public showLessValues() { this.ensureDom(); this.logger.info('Show less values'); this.dynamicFacetQueryController.resetNumberOfValuesToRequest(); this.triggerNewIsolatedQuery(() => this.logAnalyticsFacetShowMoreLess(analyticsActionCauseList.dynamicFacetShowLess)); } /** * Deselects all values in this facet. * * Does **not** trigger a query automatically. * Updates the visual of the facet. * */ public reset() { this.ensureDom(); if (!this.values.hasActiveValues) { return; } this.logger.info('Deselect all values'); this.values.clearAll(); this.values.render(); this.enablePreventAutoSelectionFlag(); this.updateAppearance(); this.updateQueryStateModel(); } /** * Collapses or expands the facet depending on it's current state. */ public toggleCollapse() { this.isCollapsed ? this.expand() : this.collapse(); } /** * Expands the facet, displaying all of its currently fetched values. */ public expand() { if (!this.options.enableCollapse) { return this.logger.warn(`Calling expand() won't do anything on a facet that has the option "enableCollapse" set to "false"`); } if (!this.isCollapsed) { return; } this.ensureDom(); this.logger.info('Expand facet values'); this.isCollapsed = false; this.updateAppearance(); } /** * Collapses the facet, displaying only its currently selected values. */ public collapse() { if (!this.options.enableCollapse) { return this.logger.warn(`Calling collapse() won't do anything on a facet that has the option "enableCollapse" set to "false"`); } if (this.isCollapsed) { return; } this.ensureDom(); this.logger.info('Collapse facet values'); this.isCollapsed = true; this.updateAppearance(); } /** * Sets a flag indicating whether the facet values should be returned in their current order. * * Setting the flag to `true` helps ensuring that the values do not move around while the end-user is interacting with them. * * The flag is automatically set back to `false` after a query is built. */ public enableFreezeCurrentValuesFlag() { this.dynamicFacetQueryController.enableFreezeCurrentValuesFlag(); } /** * For this method to work, the component has to be the child of a [DynamicFacetManager]{@link DynamicFacetManager} component. * * Sets a flag indicating whether the facets should be returned in their current order. * * Setting the flag to `true` helps ensuring that the facets do not move around while the end-user is interacting with them. * * The flag is automatically set back to `false` after a query is built. */ public enableFreezeFacetOrderFlag() { this.dynamicFacetQueryController.enableFreezeFacetOrderFlag(); } public enablePreventAutoSelectionFlag() { this.dynamicFacetQueryController.enablePreventAutoSelectionFlag(); } public scrollToTop() { if (this.options.enableScrollToTop) { ResultListUtils.scrollToTop(this.root); } } public get analyticsFacetState(): IAnalyticsFacetState[] { return this.values.activeValues.map(facetValue => facetValue.analyticsFacetState); } public get basicAnalyticsFacetState(): IAnalyticsFacetState { return { field: this.options.field.toString(), id: this.options.id, title: this.options.title, facetType: this.facetType, facetPosition: this.position }; } public get basicAnalyticsFacetMeta(): IAnalyticsFacetMeta { return { facetField: this.options.field.toString(), facetId: this.options.id, facetTitle: this.options.title }; } public logAnalyticsEvent(actionCause: IAnalyticsActionCause, facetMeta: IAnalyticsFacetMeta) { this.usageAnalytics.logSearchEvent<IAnalyticsFacetMeta>(actionCause, facetMeta); } public putStateIntoQueryBuilder(queryBuilder: QueryBuilder) { Assert.exists(queryBuilder); this.dynamicFacetQueryController.putFacetIntoQueryBuilder(queryBuilder); } public putStateIntoAnalytics() { const pendingEvent = this.usageAnalytics.getPendingSearchEvent(); pendingEvent && pendingEvent.addFacetState(this.analyticsFacetState); } public isCurrentlyDisplayed() { return $$(this.element).isVisible(); } public get hasActiveValues() { return this.values.hasActiveValues; } private initQueryEvents() { this.bind.onRootElement(QueryEvents.duringQuery, () => this.ensureDom()); this.bind.onRootElement(QueryEvents.doneBuildingQuery, (data: IDoneBuildingQueryEventArgs) => this.handleDoneBuildingQuery(data)); this.bind.onRootElement(QueryEvents.deferredQuerySuccess, (data: IQuerySuccessEventArgs) => this.handleQuerySuccess(data.results)); this.bind.onRootElement(QueryEvents.queryError, () => this.onNoValues()); } private initQueryStateEvents() { this.includedAttributeId = QueryStateModel.getFacetId(this.options.id); this.queryStateModel.registerNewAttribute(this.includedAttributeId, []); this.bind.onQueryState(MODEL_EVENTS.CHANGE, undefined, this.handleQueryStateChanged); } protected initBreadCrumbEvents() { if (this.options.includeInBreadcrumb) { this.bind.onRootElement(BreadcrumbEvents.populateBreadcrumb, (args: IPopulateBreadcrumbEventArgs) => this.handlePopulateBreadcrumb(args) ); this.bind.onRootElement(BreadcrumbEvents.clearBreadcrumb, () => this.reset()); } } protected initValues() { this.values = new DynamicFacetValues(this, DynamicFacetValueCreator); } private initComponentStateEvents() { const componentStateId = QueryStateModel.getFacetId(this.options.id); this.componentStateModel.registerComponent(componentStateId, this); } protected initDynamicFacetQueryController() { this.dynamicFacetQueryController = new DynamicFacetQueryController(this); } private handleDoneBuildingQuery(data: IDoneBuildingQueryEventArgs) { // If there is a DynamicFacetManager, it will take care of adding the facet's state if (this.dynamicFacetManager) { return; } Assert.exists(data); Assert.exists(data.queryBuilder); this.putStateIntoQueryBuilder(data.queryBuilder); this.putStateIntoAnalytics(); } private handleQuerySuccess(results: IQueryResults) { // If there is a DynamicFacetManager, it will take care of handling the results if (this.dynamicFacetManager) { return; } if (Utils.isNullOrUndefined(results.facets)) { return this.notImplementedError(); } this.handleQueryResults(results); } public handleQueryResults(results: IQueryResults) { const index = findIndex(results.facets, { facetId: this.options.id }); const facetResponse = index !== -1 ? results.facets[index] : null; this.position = facetResponse ? index + 1 : undefined; facetResponse ? this.onNewValues(facetResponse) : this.onNoValues(); this.header.hideLoading(); this.updateQueryStateModel(); this.values.render(); this.updateAppearance(); if (this.valueToFocusOnRender) { const value = this.valueToFocusOnRender; this.valueToFocusOnRender = null; defer(() => this.values.focus(value)); } } private onNewValues(facetResponse: IFacetResponse) { this.moreValuesAvailable = facetResponse.moreValuesAvailable; this.values.createFromResponse(facetResponse); if (this.options.customSort) { this.values.reorderValues(this.options.customSort); } } private onNoValues() { this.moreValuesAvailable = false; this.values.resetValues(); } private handleQueryStateChanged(data: IAttributesChangedEventArg) { if (!this.listenToQueryStateChange) { return; } const querySelectedValues: string[] = data.attributes[this.includedAttributeId]; if (!querySelectedValues) { return; } this.handleQueryStateChangedIncluded(querySelectedValues); } private handleQueryStateChangedIncluded = (querySelectedValues: string[]) => { const currentSelectedValues = this.values.selectedValues; const validQuerySelectedValues = querySelectedValues.filter(value => this.values.get(value)); const valuesToSelect = difference(validQuerySelectedValues, currentSelectedValues); const valuesToDeselect = difference(currentSelectedValues, validQuerySelectedValues); if (Utils.isNonEmptyArray(valuesToSelect)) { this.selectMultipleValues(valuesToSelect); } if (Utils.isNonEmptyArray(valuesToDeselect)) { this.deselectMultipleValues(valuesToDeselect); } }; private handlePopulateBreadcrumb(args: IPopulateBreadcrumbEventArgs) { Assert.exists(args); if (!this.values.hasActiveValues) { return; } const breadcrumbs = new DynamicFacetBreadcrumbs(this, { headingLevel: args.headingLevel }); args.breadcrumbs.push({ element: breadcrumbs.element }); } private initDependsOnManager() { const facetInfo: IDependentFacet = { reset: () => this.reset(), ref: this }; this.dependsOnManager = new DependsOnManager(facetInfo); } public createDom() { this.createAndAppendContent(); this.updateAppearance(); } private createAndAppendContent() { this.createAndAppendHeader(); this.createAndAppendSearch(); this.createAndAppendValues(); } private createAndAppendHeader() { this.header = new DynamicFacetHeader({ id: this.options.id, title: this.options.title, enableCollapse: this.options.enableCollapse, headingLevel: this.options.headingLevel, clear: () => this.clear(), toggleCollapse: () => this.toggleCollapse(), collapse: () => this.collapse(), expand: () => this.expand() }); this.element.appendChild(this.header.element); } private createAndAppendSearch() { if (this.options.enableFacetSearch === false) { return; } this.search = new DynamicFacetSearch(this); this.element.appendChild(this.search.element); } private createAndAppendValues() { this.element.appendChild(this.values.render()); } private updateQueryStateModel() { this.listenToQueryStateChange = false; this.queryStateModel.set(this.includedAttributeId, this.values.selectedValues); this.listenToQueryStateChange = true; } private updateAppearance() { this.header.toggleClear(this.values.hasSelectedValues); this.header.toggleCollapse(this.isCollapsed); this.toggleSearchDisplay(); $$(this.element).toggleClass('coveo-dynamic-facet-collapsed', this.isCollapsed); $$(this.element).toggleClass('coveo-active', this.values.hasSelectedValues); $$(this.element).toggleClass('coveo-hidden', !this.values.hasDisplayedValues); } private toggleSearchDisplay() { if (this.options.enableFacetSearch === false) { return; } if (this.isCollapsed) { return $$(this.search.element).toggleClass('coveo-hidden', true); } $$(this.search.element).toggleClass('coveo-hidden', !this.options.enableFacetSearch && !this.moreValuesAvailable); } public triggerNewQuery(beforeExecuteQuery?: () => void) { this.beforeSendingQuery(); const options: IQueryOptions = beforeExecuteQuery ? { beforeExecuteQuery } : { ignoreWarningSearchEvent: true }; this.queryController.executeQuery(options); } public async triggerNewIsolatedQuery(beforeExecuteQuery?: () => void) { this.beforeSendingQuery(); beforeExecuteQuery && beforeExecuteQuery(); try { const results = await this.dynamicFacetQueryController.getQueryResults(); this.handleQueryResults(results); } catch (e) { this.header.hideLoading(); } } private beforeSendingQuery() { this.header.showLoading(); } private notImplementedError() { this.logger.error('DynamicFacets are not supported by your current search endpoint. Disabling this component.'); this.disable(); this.updateAppearance(); } private verifyCollapsingConfiguration() { if (this.options.collapsedByDefault && !this.options.enableCollapse) { this.logger.warn('The "collapsedByDefault" option is "true" while the "enableCollapse" is "false"'); } } private logAnalyticsFacetShowMoreLess(cause: IAnalyticsActionCause) { this.usageAnalytics.logCustomEvent<IAnalyticsFacetMeta>(cause, this.basicAnalyticsFacetMeta, this.element); } private clear() { this.reset(); this.enableFreezeFacetOrderFlag(); this.scrollToTop(); this.triggerNewQuery(() => this.logClearAllToAnalytics()); } private logClearAllToAnalytics() { this.logAnalyticsEvent(analyticsActionCauseList.dynamicFacetClearAll, this.basicAnalyticsFacetMeta); } } Initialization.registerAutoCreateComponent(DynamicFacet); DynamicFacet.doExport();