coveo-search-ui
Version:
Coveo JavaScript Search Framework
807 lines (702 loc) • 29.3 kB
text/typescript
import 'styling/DynamicFacet/_DynamicFacet';
import { Component } from '../Base/Component';
import { l } from '../../strings/Strings';
import { ComponentOptions } from '../Base/ComponentOptions';
import { IComponentBindings } from '../Base/ComponentBindings';
import { $$ } from '../../utils/Dom';
import { Initialization } from '../Base/Initialization';
import { exportGlobally } from '../../GlobalExports';
import { DynamicHierarchicalFacetQueryController } from '../../controllers/DynamicHierarchicalFacetQueryController';
import { QueryStateModel } from '../../models/QueryStateModel';
import { IAttributesChangedEventArg, MODEL_EVENTS } from '../../models/Model';
import { Utils } from '../../utils/Utils';
import { isArray, findIndex, last, isEqual } from 'underscore';
import { Assert } from '../../misc/Assert';
import { QueryEvents, IQuerySuccessEventArgs, IDoneBuildingQueryEventArgs } from '../../events/QueryEvents';
import { BreadcrumbEvents, IPopulateBreadcrumbEventArgs } from '../../events/BreadcrumbEvents';
import { DynamicHierarchicalFacetBreadcrumb } from './DynamicHierarchicalFacetBreadcrumb';
import { analyticsActionCauseList, IAnalyticsActionCause, IAnalyticsFacetMeta } from '../Analytics/AnalyticsActionListMeta';
import { QueryBuilder } from '../Base/QueryBuilder';
import { ResponsiveFacets } from '../ResponsiveComponents/ResponsiveFacets';
import {
IDynamicHierarchicalFacetOptions,
IDynamicHierarchicalFacet,
IDynamicHierarchicalFacetValues,
HierarchicalFacetSortCriteria
} from './IDynamicHierarchicalFacet';
import { ResponsiveFacetOptions } from '../ResponsiveComponents/ResponsiveFacetOptions';
import { DynamicFacetHeader } from '../DynamicFacet/DynamicFacetHeader/DynamicFacetHeader';
import { IStringMap } from '../../rest/GenericParam';
import { DependsOnManager, IDependentFacet, IDependentFacetCondition } from '../../utils/DependsOnManager';
import { ResultListUtils } from '../../utils/ResultListUtils';
import { FacetType } from '../../rest/Facet/FacetRequest';
import { DynamicHierarchicalFacetValues } from './DynamicHierarchicalFacetValues/DynamicHierarchicalFacetValues';
import { IFacetResponse } from '../../rest/Facet/FacetResponse';
import { IQueryOptions } from '../../controllers/QueryController';
import { IQueryResults } from '../../rest/QueryResults';
import { DynamicFacetManager } from '../DynamicFacetManager/DynamicFacetManager';
import { IAnalyticsFacetState } from '../Analytics/IAnalyticsFacetState';
import { FacetValueState } from '../../rest/Facet/FacetValueState';
import { FacetSortCriteria } from '../../rest/Facet/FacetSortCriteria';
import { Logger } from '../../misc/Logger';
import { DynamicHierarchicalFacetSearch } from '../DynamicHierarchicalFacetSearch/DynamicHierarchicalFacetSearch';
import { IFieldValueCompatibleFacet } from '../FieldValue/IFieldValueCompatibleFacet';
/**
* The `DynamicHierarchicalFacet` component is a facet that renders values in a hierarchical fashion. It determines the filter to apply depending on the
* selected path of values.
*
* This facet requires a [`field`]{@link DynamicHierarchicalFacet.options.field} with a special format to work correctly.
*
* @notSupportedIn salesforcefree
* @availablesince [January 2020 Release (v2.7968)](https://docs.coveo.com/en/3163/)
* @externaldocs [Using Hierarchical Facets](https://docs.coveo.com/en/2667)
*/
export class DynamicHierarchicalFacet extends Component implements IDynamicHierarchicalFacet, IFieldValueCompatibleFacet {
static ID = 'DynamicHierarchicalFacet';
static doExport = () => {
exportGlobally({
DynamicHierarchicalFacet
});
};
/**
* The options for the DynamicHierarchicalFacet
* @componentOptions
*/
static options: IDynamicHierarchicalFacetOptions = {
/**
* A unique identifier for the facet. Among other things, this identifier serves the purpose of saving
* the facet state in the URL hash.
*
* If you have two facets with the same field on the same page, you should specify an `id` value for at least one of
* those two facets. This `id` must be unique among the facets.
*
* Default value is the [`field`]{@link DynamicHierarchicalFacet.options.field} option value.
*
* @examples productcategory
*/
id: ComponentOptions.buildStringOption({
postProcessing: (value, options: IDynamicHierarchicalFacetOptions) => value || (options.field as string),
section: 'CommonOptions'
}),
/**
* The index field whose values the facet should use. The field values should have the form:
* `the; the|path; the|path|to; the|path|to|given; the|path|to|given|item;`
* where the delimiting character is `|`. This default delimiting character can be changed using the [delimitingCharacter]{@link DynamicHierarchicalFacet.options.delimitingCharacter} option.
*
* To help you verify whether your fields are setup correctly, use the [`debugInfo`]{@link DynamicHierarchicalFacet.debugInfo} method.
*
* @externaldocs [Using the Hierarchical Facet Component](https://docs.coveo.com/en/2667).
* @examples @category
*/
field: ComponentOptions.buildFieldOption({ required: true, section: 'CommonOptions' }),
/**
* The title to display at the top of the facet.
*
* Default value is the localized string for `NoTitle`.
*
* @examples Product category
*/
title: ComponentOptions.buildLocalizedStringOption({
localizedString: () => l('NoTitle'),
section: 'CommonOptions'
}),
/**
* The maximum number of field values to display by default in the facet before the user
* clicks the arrow to show more.
*
* See also the [`enableMoreLess`]{@link DynamicHierarchicalFacet.options.enableMoreLess} option.
*
* @examples 8
*/
numberOfValues: ComponentOptions.buildNumberOption({ defaultValue: 5, min: 0, section: 'CommonOptions' }),
/**
* Whether to allow the end-user to expand and collapse this facet.
*/
enableCollapse: ComponentOptions.buildBooleanOption({ defaultValue: true, section: 'CommonOptions' }),
/**
* Whether this facet should be collapsed by default.
*/
collapsedByDefault: ComponentOptions.buildBooleanOption({ defaultValue: false, section: 'CommonOptions', depend: 'enableCollapse' }),
/**
* Whether to scroll back to the top of the page whenever the end-user interacts with a facet.
*/
enableScrollToTop: ComponentOptions.buildBooleanOption({ defaultValue: true, section: 'CommonOptions' }),
/**
* Whether to notify the [`Breadcrumb`]{@link Breadcrumb} component when toggling values in the facet.
*/
includeInBreadcrumb: ComponentOptions.buildBooleanOption({ defaultValue: true, 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.
*
* @examples 1500
*/
injectionDepth: ComponentOptions.buildNumberOption({ defaultValue: 1000, min: 1000 }),
/**
* Whether to enable the **More** and **Less** buttons in the Facet.
*
* See also the [`numberOfValues`]{@link DynamicHierarchicalFacet.options.numberOfValues} option.
*/
enableMoreLess: ComponentOptions.buildBooleanOption({ defaultValue: true, section: 'CommonOptions' }),
/**
* Whether to allow the end-user to search the facet values.
*/
enableFacetSearch: ComponentOptions.buildBooleanOption({ section: 'Filtering', defaultValue: false }),
/**
* The character that specifies the hierarchical dependency.
*
* **Example:**
*
* If your field has the following values:
*
* `electronics; electronics>laptops; electronics>laptops>gaming;`
*
* The delimiting character is `>`.
*
* @examples >
*/
delimitingCharacter: ComponentOptions.buildStringOption({ defaultValue: '|', section: 'CommonOptions' }),
/**
* A mapping of facet values to their desired captions.
*
* @externaldocs [Normalizing Facet Value Captions](https://docs.coveo.com/en/368/)
*/
valueCaption: ComponentOptions.buildJsonOption<IStringMap<string>>({ defaultValue: {} }),
/**
* The `id` option value of another facet in which at least one value must be selected in order
* for the dependent hierarchical facet to be visible.
*
* **Default:** `undefined` and the hierarchical facet does not depend on any other facet to be displayed.
*
* @externaldocs [Defining Dependent Facets](https://docs.coveo.com/3210/)
* @examples department
*/
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/)
*/
dependsOnCondition: ComponentOptions.buildCustomOption<IDependentFacetCondition>(
() => {
return null;
},
{ depend: 'dependsOn', section: 'CommonOptions' }
),
/**
* 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`.
*/
filterFacetCount: ComponentOptions.buildBooleanOption({ section: 'Filtering' }),
/**
* The sort criterion to use for this facet.
*
* **Default (Search API):** `occurrences`.
*
* @examples alphanumeric
*
* @availablesince [March 2020 Release (v2.8521)](https://docs.coveo.com/en/3203/)
*/
sortCriteria: <HierarchicalFacetSortCriteria>ComponentOptions.buildStringOption({
postProcessing: value => {
if (!value) {
return undefined;
}
if (value === FacetSortCriteria.alphanumeric || value === FacetSortCriteria.occurrences) {
return value;
}
new Logger(value).warn('sortCriteria is not of the the allowed values: "alphanumeric", "occurrences"');
return undefined;
},
section: 'Sorting'
}),
/**
* Specifies a custom order by which to sort the dynamic hierarchical facet values.
*
* Custom-ordered values won't necessarily retrieve values from the index.
*
* **Example:**
* ```html
* <div
* class="CoveoDynamicHierarchicalFacet"
* data-field="@countries"
* data-title="Countries"
* data-tab="All"
* data-clear-label="All Countries"
* data-delimiting-character="|"
* data-custom-sort="America, Asia|Japan, America|Mexico, America|Canada, Europe|Germany, Europe|France, Oceania"
* ></div>
* ```
*/
customSort: ComponentOptions.buildStringOption({ section: 'Sorting' }),
/**
* If a custom sort is defined, this specifies the delimiter that separates paths from each other.
*
* *Example:**
* ```html
* <div
* class="CoveoDynamicHierarchicalFacet"
* data-field="@countries"
* data-title="Countries"
* data-tab="All"
* data-clear-label="All Countries"
* data-delimiting-character="|"
* data-custom-sort="America...Asia|Japan...America|Mexico...America|Canada...Europe|Germany...Europe|France...Oceania"
* data-custom-sort-delimiting-character="..."
* ></div>
* ```
*/
customSortDelimitingCharacter: ComponentOptions.buildStringOption({
section: 'Sorting',
depend: 'customSort',
postProcessing: (value, options: IDynamicHierarchicalFacetOptions) =>
Utils.isNullOrUndefined(value) ? (options.delimitingCharacter === ',' ? '|' : ',') : value
}),
/**
* The label to display to clear the facet when a value is selected.
*
* Default value is the localized string for `AllCategories`.
*
* @examples Everything
*/
clearLabel: ComponentOptions.buildLocalizedStringOption({
localizedString: () => l('AllCategories'),
section: 'CommonOptions'
}),
/**
* The base path shared by all values to display in the hierarchical facet.
*
* If you set this option, the specified base path will always be active on the facet.
*
* This implies that:
* - The end user will not have to manually select the specified base path values.
* - Values that do not have the specified base path will not be displayed in the facet.
*
* @examples electronics, electronics\,laptops
*
* @availablesince [April 2020 Release (v2.8864)](https://docs.coveo.com/en/3231/)
*/
basePath: ComponentOptions.buildListOption<string>({ defaultValue: [] }),
/**
* 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 }),
...ResponsiveFacetOptions
};
private listenToQueryStateChange = true;
private search: DynamicHierarchicalFacetSearch;
public options: IDynamicHierarchicalFacetOptions;
public dynamicHierarchicalFacetQueryController: DynamicHierarchicalFacetQueryController;
public dependsOnManager: DependsOnManager;
public isCollapsed: boolean;
public header: DynamicFacetHeader;
public values: IDynamicHierarchicalFacetValues;
public moreValuesAvailable = false;
public position: number;
public dynamicFacetManager: DynamicFacetManager;
public isDynamicFacet = true;
public isFieldValueCompatible = true;
public isFieldValueHierarchical = true;
constructor(public element: HTMLElement, options: IDynamicHierarchicalFacetOptions, bindings?: IComponentBindings) {
super(element, 'DynamicHierarchicalFacet', bindings);
this.options = ComponentOptions.initComponentOptions(element, DynamicHierarchicalFacet, options);
this.dynamicHierarchicalFacetQueryController = new DynamicHierarchicalFacetQueryController(this);
this.isCollapsed = this.options.enableCollapse && this.options.collapsedByDefault;
this.verifyCollapsingConfiguration();
this.values = new DynamicHierarchicalFacetValues(this);
ResponsiveFacets.init(this.root, this, this.options);
this.initDependsOnManager();
this.initBreadCrumbEvents();
this.initQueryEvents();
this.initQueryStateEvents();
}
public get fieldName() {
return this.options.field.slice(1);
}
public get facetType() {
return FacetType.hierarchical;
}
public isCurrentlyDisplayed() {
return $$(this.element).isVisible();
}
public get hasActiveValues() {
return this.values.hasSelectedValue;
}
public hasSelectedValue(value: string) {
const values = this.splitValue(value);
return isEqual(values, this.values.selectedPath);
}
public selectValue(value: string) {
this.selectPath(this.splitValue(value));
}
public deselectValue() {
this.clear();
}
public getCaptionForStringValue(value: string) {
return this.getCaption(last(this.splitValue(value)));
}
private initQueryEvents() {
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.duringQuery, () => this.ensureDom());
this.bind.onRootElement(QueryEvents.queryError, () => this.onNoValues());
}
private initBreadCrumbEvents() {
if (this.options.includeInBreadcrumb) {
this.bind.onRootElement(BreadcrumbEvents.populateBreadcrumb, (args: IPopulateBreadcrumbEventArgs) =>
this.handlePopulateBreadcrumb(args)
);
this.bind.onRootElement(BreadcrumbEvents.clearBreadcrumb, () => this.reset());
}
}
private get queryStateAttribute() {
return QueryStateModel.getFacetId(this.options.id);
}
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 verifyCollapsingConfiguration() {
if (this.options.collapsedByDefault && !this.options.enableCollapse) {
this.logger.warn('Setting "collapseByDefault" to "true" has no effect when "enableCollapse" is set to "false"');
}
}
public putStateIntoQueryBuilder(queryBuilder: QueryBuilder) {
Assert.exists(queryBuilder);
this.dynamicHierarchicalFacetQueryController.putFacetIntoQueryBuilder(queryBuilder);
}
public putStateIntoAnalytics() {
const pendingEvent = this.usageAnalytics.getPendingSearchEvent();
pendingEvent && pendingEvent.addFacetState(this.analyticsFacetState);
}
public scrollToTop() {
if (this.options.enableScrollToTop) {
ResultListUtils.scrollToTop(this.root);
}
}
private updateAppearance() {
this.header.toggleCollapse(this.isCollapsed);
this.toggleSearchDisplay();
$$(this.element).toggleClass('coveo-dynamic-hierarchical-facet-collapsed', this.isCollapsed);
$$(this.element).toggleClass('coveo-hidden', !this.values.allFacetValues.length);
}
private toggleSearchDisplay() {
if (this.options.enableFacetSearch === false) {
return;
}
return $$(this.search.element).toggleClass('coveo-hidden', this.isCollapsed);
}
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.selectedPath);
this.values.render();
this.updateAppearance();
}
private splitValue(value: string) {
return value.trim().split(this.options.delimitingCharacter);
}
private onNewValues(facetResponse: IFacetResponse) {
this.moreValuesAvailable = facetResponse.moreValuesAvailable;
this.values.createFromResponse(facetResponse);
if (this.options.customSort) {
const order = this.options.customSort.split(this.options.customSortDelimitingCharacter).map(value => this.splitValue(value));
this.values.reorderValues(order);
}
}
private onNoValues() {
this.moreValuesAvailable = false;
this.values.resetValues();
}
private updateQueryStateModel(path: string[]) {
this.listenToQueryStateChange = false;
this.queryStateModel.set(this.queryStateAttribute, path);
this.listenToQueryStateChange = true;
}
private beforeSendingQuery() {
this.header.showLoading();
}
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.dynamicHierarchicalFacetQueryController.getQueryResults();
this.handleQueryResults(results);
} catch (e) {
this.header.hideLoading();
}
}
/**
* Requests additional values.
*
* See the [`enableMoreLess`]{@link DynamicHierarchicalFacet.options.enableMoreLess} option.
*
* Automatically triggers an isolated query.
* @param additionalNumberOfValues The number of additional values to request. Minimum value is 1. Defaults to the [numberOfValues]{@link DynamicHierarchicalFacet.options.numberOfValues} option value.
*/
public showMoreValues(additionalNumberOfValues = this.options.numberOfValues) {
this.ensureDom();
this.logger.info('Show more values');
this.dynamicHierarchicalFacetQueryController.increaseNumberOfValuesToRequest(additionalNumberOfValues);
this.triggerNewIsolatedQuery(() => this.logAnalyticsFacetShowMoreLess(analyticsActionCauseList.dynamicFacetShowMore));
}
/**
* Reduces the number of displayed facet values down to [numberOfValues]{@link DynamicFacet.options.numberOfValues}.
*
* See the [`enableMoreLess`]{@link DynamicHierarchicalFacet.options.enableMoreLess} option.
*
* Automatically triggers an isolated query.
*/
public showLessValues() {
this.ensureDom();
this.logger.info('Show less values');
this.dynamicHierarchicalFacetQueryController.resetNumberOfValuesToRequest();
this.triggerNewIsolatedQuery(() => this.logAnalyticsFacetShowMoreLess(analyticsActionCauseList.dynamicFacetShowLess));
}
/**
* Select a path in the hierarchy.
*
* Does **not** trigger a query automatically.
* Does **not** update the visual of the facet until a query is performed.
*
* @param path The values representing the path.
*/
public selectPath(path: string[]) {
Assert.exists(path);
Assert.isLargerThan(0, path.length);
this.ensureDom();
this.updateQueryStateModel(path);
this.values.selectPath(path);
this.logger.info('Toggle select facet value at path', path);
}
private clear() {
this.reset();
this.scrollToTop();
this.triggerNewQuery(() => this.logAnalyticsEvent(analyticsActionCauseList.dynamicFacetClearAll));
}
/**
* Deselects the path in the facet.
*
* Does **not** trigger a query automatically.
* Does **not** update the visual of the facet until a query is performed.
*/
public reset() {
this.ensureDom();
if (!this.values.hasSelectedValue) {
return;
}
this.enablePreventAutoSelectionFlag();
this.logger.info('Deselect facet value');
this.values.clearPath();
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();
}
/**
* 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.dynamicHierarchicalFacetQueryController.enableFreezeFacetOrderFlag();
}
public enablePreventAutoSelectionFlag() {
this.dynamicHierarchicalFacetQueryController.enablePreventAutoSelectionFlag();
}
/**
* Collapses the facet, hiding 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();
}
public createDom() {
this.createAndAppendContent();
this.updateAppearance();
}
/**
*
* @param value The string to find a caption for.
* Returns the caption for a value or the value itself if no caption is available.
*/
public getCaption(value: string) {
const valueCaptions = this.options.valueCaption;
const caption = valueCaptions[value];
return caption ? caption : value;
}
public logAnalyticsEvent(actionCause: IAnalyticsActionCause) {
this.usageAnalytics.logSearchEvent<IAnalyticsFacetMeta>(actionCause, this.analyticsFacetMeta);
}
private get analyticsFacetValue() {
return this.values.selectedPath.join(this.options.delimitingCharacter);
}
public get analyticsFacetState(): IAnalyticsFacetState[] {
if (!this.values.hasSelectedValue) {
return [];
}
return [
{
field: this.options.field.toString(),
id: this.options.id,
title: this.options.title,
facetType: this.facetType,
facetPosition: this.position,
value: this.analyticsFacetValue,
displayValue: this.analyticsFacetValue,
state: FacetValueState.selected,
valuePosition: 1
}
];
}
public get analyticsFacetMeta(): IAnalyticsFacetMeta {
return {
facetField: this.options.field.toString(),
facetId: this.options.id,
facetTitle: this.options.title,
facetValue: this.analyticsFacetValue
};
}
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(),
expand: () => this.expand(),
collapse: () => this.collapse()
});
this.element.appendChild(this.header.element);
}
private createAndAppendSearch() {
if (this.options.enableFacetSearch === false) {
return;
}
this.search = new DynamicHierarchicalFacetSearch(this);
this.element.appendChild(this.search.element);
}
private createAndAppendValues() {
this.element.appendChild(this.values.render());
}
private pathIsValidForSelection(path: any): path is string[] {
return !Utils.isNullOrUndefined(path) && isArray(path);
}
private handleQueryStateChanged(data: IAttributesChangedEventArg) {
if (!this.listenToQueryStateChange) {
return;
}
const queryStatePath = data.attributes[this.queryStateAttribute];
if (!this.pathIsValidForSelection(queryStatePath)) {
return;
}
if (Utils.arrayEqual(queryStatePath, this.values.selectedPath)) {
return;
}
queryStatePath.length ? this.selectPath(queryStatePath) : this.reset();
}
private initQueryStateEvents() {
this.queryStateModel.registerNewAttribute(this.queryStateAttribute, []);
this.bind.onQueryState<IAttributesChangedEventArg>(MODEL_EVENTS.CHANGE, undefined, data => this.handleQueryStateChanged(data));
}
private initDependsOnManager() {
const facetInfo: IDependentFacet = {
reset: () => this.reset(),
ref: this
};
this.dependsOnManager = new DependsOnManager(facetInfo);
}
private notImplementedError() {
this.logger.error('DynamicHierarchicalFacets are not supported by your current search endpoint. Disabling this component.');
this.disable();
this.updateAppearance();
}
private handlePopulateBreadcrumb(args: IPopulateBreadcrumbEventArgs) {
Assert.exists(args);
if (!this.values.hasSelectedValue) {
return;
}
const breadcrumbs = new DynamicHierarchicalFacetBreadcrumb(this, { headingLevel: args.headingLevel });
args.breadcrumbs.push({ element: breadcrumbs.element });
}
private logAnalyticsFacetShowMoreLess(cause: IAnalyticsActionCause) {
this.usageAnalytics.logCustomEvent<IAnalyticsFacetMeta>(cause, this.analyticsFacetMeta, this.element);
}
}
Initialization.registerAutoCreateComponent(DynamicHierarchicalFacet);
DynamicHierarchicalFacet.doExport();