coveo-search-ui
Version:
Coveo JavaScript Search Framework
694 lines (610 loc) • 26.2 kB
text/typescript
import 'styling/_ResultsFiltersPreferences';
import * as _ from 'underscore';
import { BreadcrumbEvents, IPopulateBreadcrumbEventArgs } from '../../events/BreadcrumbEvents';
import { InitializationEvents } from '../../events/InitializationEvents';
import { PreferencesPanelEvents } from '../../events/PreferencesPanelEvents';
import { IBuildingQueryEventArgs, QueryEvents } from '../../events/QueryEvents';
import { exportGlobally } from '../../GlobalExports';
import { MODEL_EVENTS } from '../../models/Model';
import { QueryStateModel, QUERY_STATE_ATTRIBUTES } from '../../models/QueryStateModel';
import { l } from '../../strings/Strings';
import { getHeadingTag } from '../../utils/AccessibilityUtils';
import { AccessibleButton } from '../../utils/AccessibleButton';
import { $$ } from '../../utils/Dom';
import { LocalStorageUtils } from '../../utils/LocalStorageUtils';
import { SVGDom } from '../../utils/SVGDom';
import { SVGIcons } from '../../utils/SVGIcons';
import { Utils } from '../../utils/Utils';
import { analyticsActionCauseList, IAnalyticsCustomFiltersChangeMeta } from '../Analytics/AnalyticsActionListMeta';
import { Component } from '../Base/Component';
import { IComponentBindings } from '../Base/ComponentBindings';
import { ComponentOptions } from '../Base/ComponentOptions';
import { Initialization } from '../Base/Initialization';
import { Checkbox } from '../FormWidgets/Checkbox';
import { FormGroup } from '../FormWidgets/FormGroup';
import { MultiSelect } from '../FormWidgets/MultiSelect';
import { TextInput } from '../FormWidgets/TextInput';
import { Tab } from '../Tab/Tab';
export interface IResultFilterPreference {
selected?: boolean;
custom?: boolean;
tab?: string[];
caption: string;
expression: string;
}
export interface IResultsFiltersPreferencesOptions {
filters?: {
[caption: string]: { expression: string; tab?: string[] };
};
includeInBreadcrumb?: boolean;
showAdvancedFilters?: boolean;
}
/**
* The `ResultFiltersPreferences` component allows end users to create custom filters to apply to queries. These filters
* are saved to local storage.
*
* Only advanced end users who understand the Coveo query syntax should use this feature (see
* [Coveo Query Syntax Reference](https://docs.coveo.com/en/1552/searching-with-coveo/coveo-cloud-query-syntax)).
*
* This component is normally accessible through the [`Settings`]{@link Settings} menu. Its usual location in the DOM is
* inside the [`PreferencesPanel`]{@link PreferencesPanel} element.
*
* See also the {@link ResultsPreferences} component.
*/
export class ResultsFiltersPreferences extends Component {
static ID = 'ResultsFiltersPreferences';
static doExport = () => {
exportGlobally({
ResultsFiltersPreferences: ResultsFiltersPreferences
});
};
/**
* The options for the component
* @componentOptions
*/
static options: IResultsFiltersPreferencesOptions = {
/**
* Specifies whether to display the active filter(s) in the [`Breadcrumb`]{@link Breadcrumb}.
*
* Default value is `true`.
*/
includeInBreadcrumb: ComponentOptions.buildBooleanOption({ defaultValue: true }),
/**
* Specifies whether to show the **Create** button that allows the end user to create filters.
*
* If you set this option to `false`, only the pre-populated
* [`filters`]{@link ResultsFiltersPreferences.options.filters} are available to the end user.
*
* Default value is `true`.
*/
showAdvancedFilters: ComponentOptions.buildBooleanOption({ defaultValue: true }),
/**
* Specifies the default filters which all end users can apply.
*
* End users cannot modify or delete these filters. These filters do not count as "user-made" filters, but rather as
* "built-in" filters created by the developer of the search page.
*
* **Note:**
* > You cannot set this option directly in the component markup as an HTML attribute. You must either set it in the
* > [`init`]{@link init} call of your search interface (see
* > [Passing Component Options in the init Call](https://docs.coveo.com/en/346/#passing-component-options-in-the-init-call)),
* > or before the `init` call, using the `options` top-level function (see
* > [Passing Component Options Before the init Call](https://docs.coveo.com/en/346/#passing-component-options-before-the-init-call)).
*
* Filters should follow this definition:
*
* `filters : { [caption : string] : { expression : string, tab? : string[] } }`;
*
* **Example:**
*
* var myFilters = {
* "Only Google Drive Items" : {
* expression : "@connectortype == 'GoogleDriveCrawler'",
* tab : ["Tab1", "Tab2"]
* },
*
* "Another Filter" : {
* expression : [ ... another expression ... ]
* },
*
* [ ... ]
* };
*
* ```javascript
* // You can set the option in the 'init' call:
* Coveo.init(document.querySelector("#search"), {
* ResultsFiltersPreferences : {
* filters : myFilters
* }
* });
*
* // Or before the 'init' call, using the 'options' top-level function:
* // Coveo.options(document.querySelector("#search"), {
* // ResultsFiltersPreferences : {
* filters : myFilters
* }
* // });
* ```
*
* Default value is `undefined`.
*/
filters: <any>ComponentOptions.buildJsonOption()
};
public preferences: { [caption: string]: IResultFilterPreference };
public container: HTMLFieldSetElement;
private preferencePanelLocalStorage: LocalStorageUtils<{ [caption: string]: IResultFilterPreference }>;
private preferencePanel: HTMLElement;
private preferenceContainer: HTMLElement;
private preferencePanelCheckboxInput: { [caption: string]: Checkbox } = {};
private advancedFilters: HTMLElement;
private advancedFiltersBuilder: HTMLElement;
private advancedFiltersTextInputCaption: TextInput;
private advancedFiltersTextInputExpression: TextInput;
private advancedFiltersTabSelect: MultiSelect;
private advancedFilterFormValidate: HTMLFormElement;
private isFullBreadcrumbClear: boolean;
/**
* Creates a new `ResultsFiltersPreferences` component.
* @param element The HTMLElement on which to instantiate the component.
* @param options The options for the `ResultsFiltersPreferences` component.
* @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: IResultsFiltersPreferencesOptions, public bindings: IComponentBindings) {
super(element, ResultsFiltersPreferences.ID, bindings);
this.options = ComponentOptions.initComponentOptions(element, ResultsFiltersPreferences, options);
this.preferencePanel = $$(this.element).closest(Component.computeCssClassNameForType('PreferencesPanel'));
if (!this.preferencePanel) {
this.logger.warn(`Cannot instantiate ResultsFilterPreferences, as there is no "CoveoPreferencesPanel" in your page !`);
return;
}
this.preferencePanelLocalStorage = new LocalStorageUtils<{ [caption: string]: IResultFilterPreference }>(ResultsFiltersPreferences.ID);
this.mergeLocalPreferencesWithStaticPreferences();
this.bindPreferencePanelEvent();
this.bindBreadcrumbEvent();
this.bindQueryEvent();
// We need to wait after all components are initialized before building the dom, because this component interacts with Tab
// And we don't know if Tab(s) are initialized before or after this component.
this.bind.oneRootElement(InitializationEvents.afterComponentsInitialization, () => this.createDom());
this.bind.oneQueryState(MODEL_EVENTS.CHANGE_ONE, QUERY_STATE_ATTRIBUTES.T, () => this.fromPreferencesToCheckboxInput());
}
public createDom() {
this.container = <HTMLFieldSetElement>new FormGroup([], l('ResultsFilteringExpression')).build();
this.element.appendChild(this.container);
this.buildCheckboxesInput();
if (this.options.showAdvancedFilters) {
this.buildAdvancedFilters();
}
}
public save() {
this.fromCheckboxInputToPreferences();
const toSave = _.omit(this.preferences, 'tab');
this.logger.info('Saving preferences', toSave);
this.preferencePanelLocalStorage.save(toSave);
}
public exitWithoutSave() {
this.fromPreferencesToCheckboxInput();
this.hideAdvancedFilterBuilder();
}
private bindPreferencePanelEvent() {
this.bind.on(this.preferencePanel, PreferencesPanelEvents.savePreferences, () => this.save());
this.bind.on(this.preferencePanel, PreferencesPanelEvents.exitPreferencesWithoutSave, () => this.exitWithoutSave());
}
private bindBreadcrumbEvent() {
if (this.options.includeInBreadcrumb) {
this.bind.onRootElement(BreadcrumbEvents.populateBreadcrumb, (args: IPopulateBreadcrumbEventArgs) =>
this.handlePopulateBreadcrumb(args)
);
this.bind.onRootElement(BreadcrumbEvents.clearBreadcrumb, () => this.handleClearBreadcrumb());
}
}
private bindQueryEvent() {
this.bind.onRootElement(QueryEvents.buildingQuery, (args: IBuildingQueryEventArgs) => this.handleBuildingQuery(args));
}
private handleBuildingQuery(args: IBuildingQueryEventArgs) {
_.each(this.getActiveFilters(), filter => {
if (Utils.isNonEmptyString(filter.expression)) {
args.queryBuilder.advancedExpression.add(filter.expression);
}
});
}
private handlePopulateBreadcrumb(args: IPopulateBreadcrumbEventArgs) {
const actives = this.getActiveFilters();
if (Utils.isNonEmptyArray(actives)) {
const container = $$('div', { className: 'coveo-results-filter-preferences-breadcrumb' });
const title = $$(getHeadingTag(args.headingLevel, 'span'), { className: 'coveo-title' });
title.text(l('FiltersInYourPreferences') + ':');
container.el.appendChild(title.el);
const valuesContainer = $$('span', { className: 'coveo-values' });
container.el.appendChild(valuesContainer.el);
for (var i = 0; i < actives.length; i++) {
valuesContainer.el.appendChild(this.buildBreadcrumb(actives[i]));
}
args.breadcrumbs.push({ element: container.el });
}
}
private handleClearBreadcrumb() {
this.isFullBreadcrumbClear = true;
_.each(this.getActiveFilters(), filter => {
filter.selected = false;
});
this.fromPreferencesToCheckboxInput();
this.isFullBreadcrumbClear = false;
}
private buildAdvancedFilters() {
this.advancedFilters = $$('div', { className: 'coveo-advanced-filters' }, l('Create')).el;
new AccessibleButton()
.withElement(this.advancedFilters)
.withoutLabelOrTitle()
.withSelectAction(() => this.openAdvancedFilterSectionOrSaveFilters())
.build();
this.buildAdvancedFilterInput();
this.buildAdvancedFilterFormValidate();
this.advancedFiltersBuilder = $$('div', { className: 'coveo-advanced-filters-builder' }).el;
this.advancedFiltersBuilder.appendChild(this.advancedFilterFormValidate);
const onlineHelp = $$(
'a',
{
href: 'https://docs.coveo.com/en/2053/index-content/inspect-items-with-the-content-browser#result-filtering-expressions',
className: 'coveo-online-help'
},
'?'
);
const title = $$(this.container).find('.coveo-form-group-label');
onlineHelp.insertAfter(title);
$$(this.advancedFilters).insertAfter(title);
this.container.appendChild(this.advancedFiltersBuilder);
}
private buildAdvancedFilterInput() {
this.advancedFiltersTextInputCaption = new TextInput(() => {}, l('Caption'));
this.advancedFiltersTextInputCaption.getInput().setAttribute('required', '');
this.advancedFiltersTextInputExpression = new TextInput(() => {}, l('Expression'));
this.advancedFiltersTextInputExpression.getInput().setAttribute('required', '');
this.advancedFiltersTabSelect = new MultiSelect(() => {}, this.getAllTabs(), l('Tab'));
}
private buildAdvancedFilterFormValidate() {
this.advancedFilterFormValidate = <HTMLFormElement>$$('form').el;
const formSubmit = $$('input', {
type: 'submit'
});
const saveFormButton = $$(
'span',
{
className: 'coveo-save'
},
SVGIcons.icons.save
);
SVGDom.addClassToSVGInContainer(saveFormButton.el, 'coveo-save-svg');
new AccessibleButton()
.withElement(saveFormButton.el)
.withLabel(l('Save'))
.withSelectAction(() => formSubmit.el.click())
.build();
const closeFormButton = $$(
'span',
{
className: 'coveo-close'
},
SVGIcons.icons.close
);
SVGDom.addClassToSVGInContainer(closeFormButton.el, 'coveo-close-svg');
new AccessibleButton()
.withElement(closeFormButton.el)
.withLabel(l('Close'))
.withSelectAction(() => this.hideAdvancedFilterBuilder())
.build();
const saveAndCloseContainer = $$('div', {
className: 'coveo-choice-container coveo-close-and-save'
});
saveAndCloseContainer.el.appendChild(saveFormButton.el);
saveAndCloseContainer.el.appendChild(closeFormButton.el);
const inputCaption = this.advancedFiltersTextInputCaption.build();
$$(inputCaption).addClass('coveo-caption');
const filtersTabSelect = this.advancedFiltersTabSelect.build();
$$(filtersTabSelect).addClass('coveo-tab');
const filtersExpression = this.advancedFiltersTextInputExpression.build();
$$(filtersExpression).addClass('coveo-expression');
_.each([inputCaption, filtersExpression, filtersTabSelect, saveAndCloseContainer.el, formSubmit.el], (el: HTMLElement) => {
this.advancedFilterFormValidate.appendChild(el);
});
$$(this.advancedFilterFormValidate).on('submit', (e: Event) => this.validateAndSaveAdvancedFilter(e));
}
private getAllTabs() {
const tabRef = Component.getComponentRef('Tab');
if (tabRef) {
const tabsElement = $$(this.root).findAll('.' + Component.computeCssClassName(tabRef));
return _.map(tabsElement, (tabElement: HTMLElement) => {
let tab = <Tab>Component.get(tabElement);
return tab.options.id;
});
} else {
return [];
}
}
private getPreferencesBoxInputToBuild() {
return _.map(this.preferences, (filter: IResultFilterPreference) => {
return {
label: filter.caption,
tab: filter.tab,
expression: filter.expression
};
});
}
private buildCheckboxesInput() {
if (this.preferenceContainer != undefined) {
this.preferenceContainer.remove();
}
const toBuild = this.getPreferencesBoxInputToBuild();
if (Utils.isNonEmptyArray(toBuild)) {
this.preferenceContainer = $$('div', {
className: 'coveo-choices-container'
}).el;
_.each(toBuild, filterToBuild => {
const checkbox = new Checkbox((checkbox: Checkbox) => {
this.save();
const filter = this.preferences[checkbox.getValue()];
if (!this.queryController.firstQuery && !this.isFullBreadcrumbClear) {
this.fromFilterToAnalyticsEvent(filter, filter.selected ? 'selected' : 'unselected');
this.queryController.executeQuery({
closeModalBox: false
});
}
}, filterToBuild.label);
$$(checkbox.build()).addClass('coveo-choice-container');
this.preferencePanelCheckboxInput[filterToBuild.label] = checkbox;
this.preferenceContainer.appendChild(checkbox.getElement());
});
_.each($$(this.preferenceContainer).findAll('.coveo-choice-container'), (choiceContainer: HTMLElement) => {
choiceContainer.appendChild($$('div', { className: 'coveo-section coveo-section-edit-delete' }).el);
});
$$(this.container).append(this.preferenceContainer);
this.buildEditAdvancedFilter();
this.buildDeleteAdvancedFilter();
this.fromPreferencesToCheckboxInput();
}
}
private buildDeleteAdvancedFilter() {
_.each(this.preferences, filter => {
if (filter.custom) {
const deleteElement = $$(
'span',
{
className: 'coveo-delete'
},
SVGIcons.icons.delete
).el;
new AccessibleButton()
.withElement(deleteElement)
.withLabel(l('Delete'))
.withSelectAction(() => this.confirmDelete(filter, filterElement))
.build();
SVGDom.addClassToSVGInContainer(deleteElement, 'coveo-delete-svg');
const filterElement = this.getFilterElementByCaption(filter.caption);
const insertInto = $$(filterElement).find('.coveo-section-edit-delete');
insertInto.appendChild(deleteElement);
}
});
}
private buildEditAdvancedFilter() {
_.each(this.preferences, filter => {
if (filter.custom) {
const editElement = $$(
'span',
{
className: 'coveo-edit'
},
SVGIcons.icons.edit
);
new AccessibleButton()
.withElement(editElement)
.withLabel(l('Edit'))
.withSelectAction(() => this.editElement(filter, filterElement))
.build();
SVGDom.addClassToSVGInContainer(editElement.el, 'coveo-edit-svg');
const filterElement = this.getFilterElementByCaption(filter.caption);
const insertInto = $$(filterElement).find('.coveo-section-edit-delete');
insertInto.appendChild(editElement.el);
}
});
}
private buildBreadcrumb(filter: IResultFilterPreference): HTMLElement {
const elem = $$('span', { className: 'coveo-value' });
const caption = $$('span', { className: 'coveo-caption' });
caption.text(filter.caption);
elem.el.appendChild(caption.el);
const clear = $$('span', { className: 'coveo-clear' }, SVGIcons.icons.mainClear);
elem.el.appendChild(clear.el);
const onSelectAction = () => {
filter.selected = false;
this.fromFilterToAnalyticsEvent(filter, 'cleared from breadcrumb');
this.fromPreferencesToCheckboxInput();
};
new AccessibleButton().withElement(elem).withLabel(l('RemoveFilterOn', filter.caption)).withSelectAction(onSelectAction).build();
return elem.el;
}
private confirmDelete(filter: IResultFilterPreference, filterElement: HTMLElement) {
if (confirm(l('AreYouSureDeleteFilter', filter.caption, filter.expression))) {
const isSelected = filter.selected;
this.deleteFilterPreference(filter, filterElement);
if (isSelected) {
this.fromFilterToAnalyticsEvent(filter, 'deleted');
this.queryController.executeQuery({
closeModalBox: false
});
}
}
}
private editElement(filter: IResultFilterPreference, filterElement: HTMLElement) {
const oldCaption = this.preferences[filter.caption].caption;
const oldTab = this.preferences[filter.caption].tab;
const oldExpression = this.preferences[filter.caption].expression;
this.deleteFilterPreference(filter, filterElement);
this.openAdvancedFilterSectionOrSaveFilters();
this.populateEditSection({ tab: oldTab, caption: oldCaption, expression: oldExpression });
}
private populateEditSection(toPopulate = { tab: [''], caption: '', expression: '' }) {
this.advancedFiltersTextInputCaption.setValue(toPopulate.caption);
this.advancedFiltersTextInputExpression.setValue(toPopulate.expression);
this.advancedFiltersTabSelect.setValue(toPopulate.tab);
}
private deleteFilterPreference(filter: IResultFilterPreference, filterElement: HTMLElement) {
this.preferencePanelLocalStorage.remove(filter.caption);
delete this.preferences[filter.caption];
$$($$(filterElement).closest('.coveo-choice-container')).detach();
}
private openAdvancedFilterSectionOrSaveFilters() {
if ($$(this.advancedFiltersBuilder).hasClass('coveo-active')) {
$$($$(this.advancedFilterFormValidate).find('input[type=submit]')).trigger('click');
this.hideAdvancedFilterBuilder();
} else {
this.populateEditSection();
this.showAdvancedFilterBuilder();
}
}
private validateAndSaveAdvancedFilter(e: Event) {
e.preventDefault();
this.hideAdvancedFilterBuilder();
const caption = this.advancedFiltersTextInputCaption.getValue();
const expression = this.advancedFiltersTextInputExpression.getValue();
const tabs = this.advancedFiltersTabSelect.getValue();
this.preferences[caption] = {
caption: caption,
custom: true,
expression: expression,
tab: tabs,
selected: true
};
this.buildCheckboxesInput();
this.save();
this.queryStateModel.set(QueryStateModel.attributesEnum.t, this.getActiveTab());
this.advancedFiltersTextInputCaption.reset();
this.advancedFiltersTextInputExpression.reset();
this.advancedFiltersTabSelect.reset();
this.container.appendChild(this.advancedFiltersBuilder);
this.fromFilterToAnalyticsEvent(this.preferences[caption], 'saved');
this.queryController.executeQuery({
closeModalBox: false
});
}
private fromPreferencesToCheckboxInput() {
_.each(this.getActiveFilters(), (filter: IResultFilterPreference) => {
this.preferencePanelCheckboxInput[filter.caption].select();
});
_.each(this.getInactiveFilters(), (filter: IResultFilterPreference) => {
this.preferencePanelCheckboxInput[filter.caption].reset();
});
_.each(this.getDormantFilters(), (filter: IResultFilterPreference) => {
this.preferencePanelCheckboxInput[filter.caption].select();
});
}
private fromCheckboxInputToPreferences() {
if (this.preferencePanelCheckboxInput) {
const selecteds = _.map(
_.filter(this.preferencePanelCheckboxInput, (checkbox: Checkbox) => checkbox.isSelected()),
(selected: Checkbox) => selected.getValue()
);
_.each(this.preferences, (filter: IResultFilterPreference) => {
if (_.contains(selecteds, filter.caption)) {
filter.selected = true;
} else {
filter.selected = false;
}
});
}
}
private getDormantFilters() {
const activeTab = this.getActiveTab();
return _.filter(this.preferences, (filter: IResultFilterPreference) => {
return filter.selected && !this.filterIsInActiveTab(filter, activeTab);
});
}
private getActiveFilters() {
const activeTab = this.getActiveTab();
return _.filter(this.preferences, (filter: IResultFilterPreference) => {
return filter.selected && this.filterIsInActiveTab(filter, activeTab);
});
}
private getInactiveFilters() {
const activeTab = this.getActiveTab();
return _.filter(this.preferences, (filter: IResultFilterPreference) => {
return !filter.selected || !this.filterIsInActiveTab(filter, activeTab);
});
}
private getActiveTab() {
return this.queryStateModel.get(QueryStateModel.attributesEnum.t);
}
private filterIsInActiveTab(filter: IResultFilterPreference, tab: string) {
filter.tab = _.compact(filter.tab);
return _.contains(filter.tab, tab) || Utils.isEmptyArray(filter.tab);
}
private getFilterElementByCaption(caption: string): HTMLElement {
return $$(this.preferenceContainer).find("input[value='" + caption.replace(/'/g, "\\'") + "']").parentElement;
}
private fromResultsFilterOptionToResultsPreferenceInterface() {
const ret: { [key: string]: IResultFilterPreference } = {};
_.each(<any>this.options.filters, (filter: { expression: string; tab?: string[]; selected?: boolean }, caption: string) => {
ret[caption] = {
expression: filter.expression,
tab: filter.tab,
selected: filter.selected ? filter.selected : false,
custom: false,
caption: caption
};
});
return ret;
}
private mergeLocalPreferencesWithStaticPreferences() {
const staticPreferences = this.fromResultsFilterOptionToResultsPreferenceInterface();
const localPreferences = this.preferencePanelLocalStorage.load();
const localPreferencesWithoutRemoved = _.filter<IResultFilterPreference>(localPreferences, preference => {
const isCustom = preference.custom;
const existsInStatic = _.find<IResultFilterPreference>(staticPreferences, staticPreference => {
return staticPreference.caption == preference.caption;
});
return isCustom || existsInStatic != undefined;
});
const localToMerge = {};
_.each(localPreferencesWithoutRemoved, (filter: IResultFilterPreference) => {
localToMerge[filter.caption] = {
expression: filter.expression,
tab: filter.tab,
selected: filter.selected,
custom: filter.custom,
caption: filter.caption
};
});
this.preferences = <{ [caption: string]: IResultFilterPreference }>Utils.extendDeep(staticPreferences, localToMerge);
}
private fromFilterToAnalyticsEvent(filter: IResultFilterPreference, type: string) {
this.usageAnalytics.logSearchEvent<IAnalyticsCustomFiltersChangeMeta>(analyticsActionCauseList.customfiltersChange, {
customFilterName: filter.caption,
customFilterExpression: filter.expression,
customFilterType: type
});
}
private enlargeModalBox() {
const modalBoxContainer = $$(document.body).find('.coveo-modal-container');
if (modalBoxContainer) {
$$(modalBoxContainer).addClass('coveo-mod-big');
}
}
private shrinkModalBox() {
const modalBoxContainer = $$(document.body).find('.coveo-modal-container');
if (modalBoxContainer) {
$$(modalBoxContainer).removeClass('coveo-mod-big');
}
}
private showAdvancedFilterBuilder() {
if (this.advancedFiltersBuilder) {
$$(this.advancedFiltersBuilder).addClass('coveo-active');
this.enlargeModalBox();
}
}
private hideAdvancedFilterBuilder() {
if (this.advancedFiltersBuilder) {
$$(this.advancedFiltersBuilder).removeClass('coveo-active');
this.shrinkModalBox();
}
}
}
Initialization.registerAutoCreateComponent(ResultsFiltersPreferences);