coveo-search-ui
Version:
Coveo JavaScript Search Framework
186 lines (156 loc) • 6.57 kB
text/typescript
import 'styling/_SortDropdown';
import { findIndex } from 'underscore';
import { exportGlobally } from '../../GlobalExports';
import { IQuerySuccessEventArgs, IQueryErrorEventArgs, QueryEvents } from '../../events/QueryEvents';
import { IAttributeChangedEventArg, MODEL_EVENTS } from '../../models/Model';
import { QUERY_STATE_ATTRIBUTES, QueryStateModel } from '../../models/QueryStateModel';
import { $$ } from '../../utils/Dom';
import { Component } from '../Base/Component';
import { IComponentBindings } from '../Base/ComponentBindings';
import { ComponentOptions } from '../Base/ComponentOptions';
import { Initialization } from '../Base/Initialization';
import { InitializationEvents } from '../../events/InitializationEvents';
import { Sort } from '../Sort/Sort';
import { Dropdown } from '../FormWidgets/Dropdown';
import { l } from '../../strings/Strings';
/**
* The `SortDropdown` component renders a dropdown that the end user can interact with to select the criteria to use when sorting query results.
*
* It is meant to be a parent of regular [`Sort`]{@link Sort} components. Example:
* ```
* <div class="CoveoSortDropdown">
* <span class="CoveoSort" data-sort-criteria="relevancy" data-caption="Relevance"></span>
* <span class="CoveoSort" data-sort-criteria="date descending" data-caption="Newest"></span>
* <span class="CoveoSort" data-sort-criteria="date ascending" data-caption="Oldest"></span>
* </div>
* ```
* Each one of the children `Sort` components should have only one sort criteria to prevent the regular toggle behaviour.
*
* @availablesince [March 2020 Release (v2.8521)](https://docs.coveo.com/en/3203/)
*/
export class SortDropdown extends Component {
static ID = 'SortDropdown';
static options: any = {};
static doExport = () => {
exportGlobally({
SortDropdown: SortDropdown
});
};
private dropdown: Dropdown;
private sortComponents: Sort[] = [];
/**
* Creates a new `SortDropdown` component instance.
* @param element The HTMLElement on which to instantiate the component.
* @param options The options for this component instance.
* @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?: any, bindings?: IComponentBindings) {
super(element, SortDropdown.ID, bindings);
this.options = ComponentOptions.initComponentOptions(element, SortDropdown, options);
this.removeTabSupport();
this.bind.oneRootElement(InitializationEvents.afterInitialization, () => this.handleAfterInitialization());
this.bind.onQueryState(MODEL_EVENTS.CHANGE_ONE, QUERY_STATE_ATTRIBUTES.SORT, (args: IAttributeChangedEventArg) =>
this.handleQueryStateChanged(args)
);
this.bind.onRootElement(QueryEvents.querySuccess, (args: IQuerySuccessEventArgs) => this.handleQuerySuccess(args));
this.bind.onRootElement(QueryEvents.queryError, (args: IQueryErrorEventArgs) => this.handleQueryError(args));
}
private handleAfterInitialization() {
this.buildDropdown();
}
private clearDropdown(): boolean {
const hasFocus = this.dropdown && document.activeElement === this.dropdown.getElement();
this.dropdown && this.element.removeChild(this.dropdown.getElement());
this.dropdown = null;
return hasFocus;
}
private buildDropdown() {
this.sortComponents = this.getEnabledSortComponents();
const hasFocus = this.clearDropdown();
if (!this.sortComponents.length) {
return;
}
this.dropdown = new Dropdown(
() => this.handleChange(),
this.getValuesForDropdown(),
value => this.getCaptionForValue(value),
l('SortBy')
);
this.element.appendChild(this.dropdown.getElement());
this.update();
hasFocus && this.dropdown.getElement().focus();
}
private getEnabledSortComponents() {
const sortComponents = $$(this.element)
.findAll(`.${Component.computeCssClassNameForType('Sort')}`)
.map(sortElement => {
const sortCmp = <Sort>Component.get(sortElement, Sort);
if (sortCmp.options.sortCriteria.length == 1) {
return sortCmp;
} else {
this.logger.warn(
`Each Sort component inside a SortDropdown should have only one sort criteria. Skipping ${sortCmp.options.caption} in the SortDropdown.`
);
return;
}
})
.filter(sortCmp => sortCmp && !sortCmp.disabled);
return sortComponents;
}
private getValuesForDropdown(): string[] {
return this.sortComponents.map(sort => sort.options.sortCriteria.toString());
}
private handleQueryStateChanged(data: IAttributeChangedEventArg) {
this.update();
}
private update() {
if (!this.dropdown) {
return;
}
const sortCriteria = <string>this.queryStateModel.get(QueryStateModel.attributesEnum.sort);
this.select(sortCriteria);
}
/**
* Selects a sort criteria from the options.
* @param sortCriteria The sort criteria to select.
* @param executeQuery Whether to execute a query after changing the sort criteria
*/
public select(sortCriteria: string, executeQuery: boolean = false) {
const sortIndex = this.getSortIndex(sortCriteria);
sortIndex > -1 && this.dropdown.select(sortIndex, executeQuery);
$$(this.dropdown.getElement()).toggleClass('coveo-selected', sortIndex > -1);
}
private handleQuerySuccess(data: IQuerySuccessEventArgs) {
if (!data.results.results.length) {
return this.hideElement();
}
this.buildDropdown();
if (!this.sortComponents.length) {
return this.hideElement();
}
this.showElement();
}
private handleQueryError(data: IQueryErrorEventArgs) {
this.hideElement();
}
private handleChange() {
const selectedValue = this.dropdown.getValue();
const sortIndex = this.getSortIndex(selectedValue);
sortIndex > -1 && this.sortComponents[sortIndex].selectAndExecuteQuery();
}
private getCaptionForValue(value: string) {
const sortIndex = this.getSortIndex(value);
return sortIndex > -1 ? this.sortComponents[sortIndex].options.caption : '';
}
private getSortIndex(itemValue: string) {
return findIndex(this.sortComponents, sort => sort.options.sortCriteria.toString() === itemValue);
}
private hideElement() {
$$(this.element).hide();
}
private showElement() {
$$(this.element).show();
}
}
Initialization.registerAutoCreateComponent(SortDropdown);