coveo-search-ui
Version:
Coveo JavaScript Search Framework
294 lines (256 loc) • 11.7 kB
text/typescript
import 'styling/_ResponsiveFacets';
import { IQuerySuccessEventArgs, QueryEvents } from '../../events/QueryEvents';
import { Logger } from '../../misc/Logger';
import { l } from '../../strings/Strings';
import { $$, Dom } from '../../utils/Dom';
import { Utils } from '../../utils/Utils';
import { Component } from '../Base/Component';
import { SearchInterface } from '../SearchInterface/SearchInterface';
import { ResponsiveComponents } from './ResponsiveComponents';
import { IResponsiveComponent, IResponsiveComponentOptions, ResponsiveComponentsManager } from './ResponsiveComponentsManager';
import { ResponsiveComponentsUtils } from './ResponsiveComponentsUtils';
import { ResponsiveDropdown } from './ResponsiveDropdown/ResponsiveDropdown';
import { ResponsiveDropdownContent, IResponsiveDropdownContent } from './ResponsiveDropdown/ResponsiveDropdownContent';
import { ResponsiveDropdownHeader } from './ResponsiveDropdown/ResponsiveDropdownHeader';
import { each, debounce } from 'underscore';
import { ComponentsTypes } from '../../utils/ComponentsTypes';
import { ResponsiveDropdownModalContent } from './ResponsiveDropdown/ResponsiveDropdownModalContent';
import { FacetsMobileMode, IFacetsMobileModeOptions } from '../FacetsMobileMode/FacetsMobileMode';
import { FacetsMobileModeEvents } from '../../events/FacetsMobileModeEvents';
export class ResponsiveFacetColumn implements IResponsiveComponent {
public static DEBOUNCE_SCROLL_WAIT = 250;
private static logger = new Logger('ResponsiveFacets');
private static DROPDOWN_MIN_WIDTH: number = 280;
private static DROPDOWN_WIDTH_RATIO: number = 0.35; // Used to set the width relative to the coveo root.
private static DROPDOWN_HEADER_LABEL_DEFAULT_VALUE = 'Filters';
private searchInterface: SearchInterface;
private componentsInFacetColumn: any[] = [];
private preservePositionOriginalValues: boolean[] = [];
private breakpoint: number;
protected dropdown: ResponsiveDropdown;
private dropdownHeaderLabel: string;
public static init(responsiveComponentConstructor, root: HTMLElement, component, options: IResponsiveComponentOptions, ID: string) {
const column = this.findColumn(root);
if (!column) {
return;
}
ResponsiveComponentsManager.register(ResponsiveFacetColumn, $$(root), 'ResponsiveFacetColumn', component, options);
ResponsiveComponentsManager.register(responsiveComponentConstructor, $$(root), ID, component, options);
}
private static findColumn(root: HTMLElement) {
const column = $$(root).find('.coveo-facet-column');
if (!column) {
this.logger.info('No element with class coveo-facet-column. Responsive facets cannot be enabled');
}
return column;
}
constructor(public coveoRoot: Dom, public ID: string, options: IResponsiveComponentOptions, responsiveDropdown?: ResponsiveDropdown) {
this.searchInterface = <SearchInterface>Component.get(this.coveoRoot.el, SearchInterface, false);
this.dropdownHeaderLabel = this.getDropdownHeaderLabel();
this.dropdown = this.buildDropdown(responsiveDropdown);
this.bindDropdownContentEvents();
this.bindFacetsMobileModeEvents();
this.registerOnCloseHandler();
this.registerQueryEvents();
this.initializeBreakpoint(options.responsiveBreakpoint);
}
private get facetsMobileModeComponent() {
return this.searchInterface ? this.searchInterface.getComponents<FacetsMobileMode>(FacetsMobileMode.ID)[0] : null;
}
private get facetsMobileModeOptions(): IFacetsMobileModeOptions {
const facetsMobileModeComponent = this.facetsMobileModeComponent;
if (!facetsMobileModeComponent) {
ResponsiveFacetColumn.logger.warn('The "FacetsMobileMode" component did not load properly, disabling the ResponsiveFacets modal.');
return {
isModal: false,
preventScrolling: false,
displayOverlayWhileOpen: true
};
}
return facetsMobileModeComponent.options;
}
public registerComponent(accept: Component) {
this.componentsInFacetColumn.push(accept);
this.preservePositionOriginalValues.push(accept.options.preservePosition);
return true;
}
public needDropdownWrapper() {
return this.needSmallMode();
}
public handleResizeEvent() {
if (this.needSmallMode() && !ResponsiveComponentsUtils.isSmallFacetActivated(this.coveoRoot)) {
this.changeToSmallMode();
} else if (!this.needSmallMode() && ResponsiveComponentsUtils.isSmallFacetActivated(this.coveoRoot)) {
this.changeToLargeMode();
}
if (this.dropdown.isOpened) {
this.dropdown.dropdownContent.positionDropdown();
}
}
public dismissFacetSearches() {
each(this.componentsInFacetColumn, component => {
if (component.facetSearch && component.facetSearch.currentlyDisplayedResults) {
component.facetSearch.dismissSearchResults();
}
});
}
private needSmallMode(): boolean {
if (!this.searchInterface) {
return (
this.coveoRoot.width() <=
(Utils.isNullOrUndefined(this.breakpoint) ? new ResponsiveComponents().getMediumScreenWidth() : this.breakpoint)
);
}
switch (this.searchInterface.responsiveComponents.getResponsiveMode()) {
case 'small':
case 'medium':
return true;
case 'auto':
return (
this.coveoRoot.width() <=
(Utils.isNullOrUndefined(this.breakpoint) ? this.searchInterface.responsiveComponents.getMediumScreenWidth() : this.breakpoint)
);
default:
return false;
}
}
private changeToSmallMode() {
this.dropdown.close();
this.disableFacetPreservePosition();
$$(this.coveoRoot.find(`.${ResponsiveComponentsManager.DROPDOWN_HEADER_WRAPPER_CSS_CLASS}`)).append(
this.dropdown.dropdownHeader.element.el
);
ResponsiveComponentsUtils.activateSmallFacet(this.coveoRoot);
}
private changeToLargeMode() {
this.restoreFacetPreservePositionValue();
this.dropdown.cleanUp();
ResponsiveComponentsUtils.deactivateSmallFacet(this.coveoRoot);
}
private buildDropdown(responsiveDropdown?: ResponsiveDropdown) {
let dropdownContent = this.buildDropdownContent();
let dropdownHeader = this.buildDropdownHeader();
let dropdown = responsiveDropdown ? responsiveDropdown : new ResponsiveDropdown(dropdownContent, dropdownHeader, this.coveoRoot);
if (!this.facetsMobileModeOptions.displayOverlayWhileOpen) {
dropdown.disablePopupBackground();
}
if (this.facetsMobileModeOptions.preventScrolling) {
dropdown.enableScrollLocking(this.facetsMobileModeOptions.scrollContainer);
}
return dropdown;
}
private buildDropdownContent(): IResponsiveDropdownContent {
let dropdownContentElement = $$(this.coveoRoot.find('.coveo-facet-column'));
let filterByContainer = $$('div', { className: 'coveo-facet-header-filter-by-container', style: 'display: none' });
let filterBy = $$('div', { className: 'coveo-facet-header-filter-by' });
filterBy.text(l('Filter by:'));
filterByContainer.append(filterBy.el);
dropdownContentElement.prepend(filterByContainer.el);
if (this.facetsMobileModeOptions.isModal) {
return new ResponsiveDropdownModalContent('facet', dropdownContentElement, l('CloseFiltersDropdown'), () => this.dropdown.close());
}
return new ResponsiveDropdownContent(
'facet',
dropdownContentElement,
this.coveoRoot,
ResponsiveFacetColumn.DROPDOWN_MIN_WIDTH,
ResponsiveFacetColumn.DROPDOWN_WIDTH_RATIO
);
}
private buildDropdownHeader() {
let dropdownHeaderElement = $$('a');
let content = $$('p');
content.text(this.dropdownHeaderLabel);
dropdownHeaderElement.el.appendChild(content.el);
let dropdownHeader = new ResponsiveDropdownHeader('facet', dropdownHeaderElement);
return dropdownHeader;
}
private initializeBreakpoint(defaultBreakpoint: number) {
const facetsMobileModeBreakpoint = this.facetsMobileModeOptions.breakpoint;
this.breakpoint = Utils.isNullOrUndefined(facetsMobileModeBreakpoint) ? defaultBreakpoint : facetsMobileModeBreakpoint;
}
private registerOnCloseHandler() {
this.dropdown.registerOnCloseHandler(this.dismissFacetSearches, this);
}
private registerQueryEvents() {
this.coveoRoot.on(QueryEvents.noResults, () => this.handleNoResults());
this.coveoRoot.on(QueryEvents.querySuccess, (e: Event, data: IQuerySuccessEventArgs) => this.handleQuerySuccess(data));
this.coveoRoot.on(QueryEvents.queryError, () => this.handleQueryError());
}
private bindDropdownContentEvents() {
this.dropdown.dropdownContent.element.on(
'scroll',
debounce(() => {
each(this.componentsInFacetColumn, component => {
let facetSearch = component.facetSearch;
if (facetSearch && facetSearch.currentlyDisplayedResults && !this.isFacetSearchScrolledIntoView(facetSearch.search)) {
component.facetSearch.positionSearchResults(this.dropdown.dropdownContent.element.el);
} else if (facetSearch && component.facetSearch.currentlyDisplayedResults) {
component.facetSearch.positionSearchResults();
}
});
}, ResponsiveFacetColumn.DEBOUNCE_SCROLL_WAIT)
);
}
private bindFacetsMobileModeEvents() {
const facetsMobileModeComponent = this.facetsMobileModeComponent;
if (facetsMobileModeComponent) {
this.dropdown.registerOnOpenHandler(
() => $$(facetsMobileModeComponent.element).trigger(FacetsMobileModeEvents.popupOpened),
facetsMobileModeComponent
);
this.dropdown.registerOnCloseHandler(
() => $$(facetsMobileModeComponent.element).trigger(FacetsMobileModeEvents.popupClosed),
facetsMobileModeComponent
);
}
}
private restoreFacetPreservePositionValue() {
each(this.componentsInFacetColumn, (component, index) => {
if (component.options) {
component.options.preservePosition = this.preservePositionOriginalValues[index];
}
});
}
private disableFacetPreservePosition() {
each(this.componentsInFacetColumn, component => {
if (component.options) {
component.options.preservePosition = false;
}
});
}
private isFacetSearchScrolledIntoView(facetSearchElement: HTMLElement) {
let facetTop = facetSearchElement.getBoundingClientRect().top;
let facetBottom = facetSearchElement.getBoundingClientRect().bottom;
let dropdownTop = this.dropdown.dropdownContent.element.el.getBoundingClientRect().top;
let dropdownBottom = this.dropdown.dropdownContent.element.el.getBoundingClientRect().bottom;
dropdownTop = dropdownTop >= 0 ? dropdownTop : 0;
return facetTop >= dropdownTop && facetBottom <= dropdownBottom;
}
private getDropdownHeaderLabel() {
let dropdownHeaderLabel: string;
ComponentsTypes.getAllFacetInstancesFromElement(this.coveoRoot.find('.coveo-facet-column')).forEach(facet => {
const options = facet.options as IResponsiveComponentOptions;
if (!dropdownHeaderLabel && options.dropdownHeaderLabel) {
dropdownHeaderLabel = options.dropdownHeaderLabel;
}
});
if (!dropdownHeaderLabel) {
dropdownHeaderLabel = l(ResponsiveFacetColumn.DROPDOWN_HEADER_LABEL_DEFAULT_VALUE);
}
return dropdownHeaderLabel;
}
private handleNoResults() {
this.dropdown.dropdownHeader.hide();
}
private handleQueryError() {
this.dropdown.dropdownHeader.hide();
}
private handleQuerySuccess(data: IQuerySuccessEventArgs) {
if (data.results.totalCount === 0) {
this.dropdown.dropdownHeader.hide();
} else {
this.dropdown.dropdownHeader.show();
}
}
}