coveo-search-ui
Version:
Coveo JavaScript Search Framework
211 lines (184 loc) • 8.14 kB
text/typescript
/// <reference path='../Facet/Facet.ts' />
import { IFacetOptions, Facet } from '../Facet/Facet';
import { IRangeValue } from '../../rest/RangeValue';
import { ComponentOptions } from '../Base/ComponentOptions';
import { IComponentBindings } from '../Base/ComponentBindings';
import { TemplateHelpers } from '../Templates/TemplateHelpers';
import { IDateToStringOptions } from '../../utils/DateUtils';
import { FacetRangeQueryController } from '../../controllers/FacetRangeQueryController';
import { IGroupByResult } from '../../rest/GroupByResult';
import { Initialization } from '../Base/Initialization';
import * as Globalize from 'globalize';
import { exportGlobally } from '../../GlobalExports';
import { ResponsiveFacetOptions } from '../ResponsiveComponents/ResponsiveFacetOptions';
import { ResponsiveFacets } from '../ResponsiveComponents/ResponsiveFacets';
import { FacetValue } from '../Facet/FacetValue';
import { isUndefined, find } from 'underscore';
export interface IFacetRangeOptions extends IFacetOptions {
ranges?: IRangeValue[];
dateField?: boolean;
valueFormat?: string;
}
/**
* A `FacetRange` is a [facet](https://docs.coveo.com/en/198/) whose values are expressed as ranges.
*
* You must set the [`field`]{@link Facet.options.field} option to a value targeting a numeric or date [field](https://docs.coveo.com/en/200/) in your index for this component to work.
*
* This component extends the [`Facet`]{@link Facet} component and supports all `Facet` options except:
*
* - **Settings** menu options
* - [`enableSettings`]{@link Facet.options.enableSettings}
* - [`enableSettingsFacetState`]{@link Facet.options.enableSettingsFacetState}
* - [`enableCollapse`]{@link Facet.options.enableCollapse}
* - [`availableSorts`]{@link Facet.options.availableSorts}
* - [`customSort`]{@link Facet.options.customSort}
* - [`computedFieldCaption`]{@link Facet.options.computedFieldCaption}
* - **Facet Search** options
* - [`enableFacetSearch`]{@link Facet.options.enableFacetSearch}
* - [`facetSearchDelay`]{@link Facet.options.facetSearchDelay}
* - [`facetSearchIgnoreAccents`]{@link Facet.options.facetSearchIgnoreAccents}
* - [`numberOfValuesInFacetSearch`]{@link Facet.options.numberOfValuesInFacetSearch}
* - **More and Less** options
* - [`enableMoreLess`]{@link Facet.options.enableMoreLess}
* - [`pageSize`]{@link Facet.options.pageSize}
*
* @notSupportedIn salesforcefree
*/
export class FacetRange extends Facet implements IComponentBindings {
static ID = 'FacetRange';
static parent = Facet;
static doExport = () => {
exportGlobally({
FacetRange: FacetRange
});
};
/**
* The options for the component
* @componentOptions
*/
static options: IFacetRangeOptions = {
/**
* Whether the specified [`field`]{@link Facet.options.field} option value targets a date field in your index.
*
* This allows the component to correctly build the outgoing [Group By](https://docs.coveo.com/en/203/).
*
* **Default:** `false`.
*/
dateField: ComponentOptions.buildBooleanOption({ defaultValue: false }),
/**
* The list of [range values]{@link IRangeValue} to request (see [Requesting Specific FacetRange Values](https://docs.coveo.com/en/2790/)).
*
* By default, the index automatically generates range values.
*
* **Note:**
* > The index cannot automatically generate range values for a `FacetRange` whose [`field`]{@link Facet.options.field} option value references a dynamic field generated by a [query function](https://docs.coveo.com/en/232/). In such a case, you _must_ use the `ranges` option.
*/
ranges: ComponentOptions.buildJsonOption<IRangeValue[]>(),
/**
* The format to apply to the range values. Only works for numeric values.
*
* Some of the most commonly used formats are:
*
* - `c0`: format a numeric value as currency.
* - `n0`: formats a numeric value as an integer.
* - `n2`: formats a numeric value as a floating point number with two decimal digits.
*
* The available formats are defined in the [Globalize](https://github.com/klaaspieter/jquery-global#numbers) library.
*
* **Note:** This option is ignored when the [`valueCaption`]{@link Facet.options.valueCaption} is defined.
*/
valueFormat: ComponentOptions.buildStringOption({ defaultValue: 'n0' }),
...ResponsiveFacetOptions
};
public options: IFacetRangeOptions;
public isFieldValueCompatible = false;
/**
* Creates a new `FacetRange`.
* @param element The HTML element on which to instantiate the component.
* @param options The configuration options to apply when creating the component.
* @param bindings The bindings required by the component.
*/
constructor(public element: HTMLElement, options: IFacetRangeOptions, bindings?: IComponentBindings) {
super(element, ComponentOptions.initComponentOptions(element, FacetRange, options), bindings, FacetRange.ID);
this.options.enableFacetSearch = false;
this.options.enableSettings = false;
this.options.includeInOmnibox = false;
this.options.enableMoreLess = false;
ResponsiveFacets.init(this.root, this, this.options);
}
public getValueCaption(facetValue: FacetValue): string {
if (this.options.valueCaption || this.isLabelSpecifiedForValue(facetValue)) {
return super.getValueCaption(facetValue);
}
return this.translateValuesFromFormat(facetValue);
}
private isLabelSpecifiedForValue(facetValue: FacetValue) {
return this.options.ranges && !!find(this.options.ranges, range => !isUndefined(range.label) && range.label === facetValue.lookupValue);
}
protected initFacetQueryController() {
this.facetQueryController = new FacetRangeQueryController(this);
}
protected processNewGroupByResults(groupByResults: IGroupByResult) {
if (groupByResults != null && this.options.ranges == null) {
groupByResults.values.sort((valueA, valueB) => this.sortRangeGroupByResults(valueA.value, valueB.value));
}
super.processNewGroupByResults(groupByResults);
}
private sortRangeGroupByResults(valueA: string, valueB: string) {
const startEndA = this.extractStartAndEndValue(valueA);
const startEndB = this.extractStartAndEndValue(valueB);
let firstValue: string;
let secondValue: string;
if (!startEndA) {
firstValue = valueA;
} else {
firstValue = startEndA.start;
}
if (!startEndB) {
secondValue = valueB;
} else {
secondValue = startEndB.start;
}
if (this.options.dateField) {
return Date.parse(firstValue) - Date.parse(secondValue);
}
return Number(firstValue) - Number(secondValue);
}
private translateValuesFromFormat(facetValue: FacetValue) {
const startAndEnd = this.extractStartAndEndValue(facetValue.lookupValue || facetValue.value);
if (!startAndEnd) {
return null;
}
return `${this.formatValue(startAndEnd.start)} - ${this.formatValue(startAndEnd.end)}`;
}
private extractStartAndEndValue(value: string) {
const startAndEnd = /^(.*)\.\.(.*)$/.exec(value);
if (startAndEnd == null) {
return null;
}
return {
start: startAndEnd[1],
end: startAndEnd[2]
};
}
private formatValue(value: string) {
const isNumber = !!value.match(/^[\+\-]?[0-9]+(\.[0-9]+)?$/);
return this.options.dateField || !isNumber ? this.formatDateValue(value) : this.formatNumberValue(Number(value));
}
private formatDateValue(value: string) {
const helper = TemplateHelpers.getHelper('dateTime');
const helperOptions: IDateToStringOptions = {
alwaysIncludeTime: false,
includeTimeIfThisWeek: false,
includeTimeIfToday: false,
omitYearIfCurrentOne: false,
useTodayYesterdayAndTomorrow: false,
useWeekdayIfThisWeek: false
};
return helper(value, helperOptions);
}
private formatNumberValue(value: Number): string {
return Globalize.format(value, this.options.valueFormat);
}
}
Initialization.registerAutoCreateComponent(FacetRange);