coveo-search-ui
Version:
Coveo JavaScript Search Framework
205 lines (177 loc) • 7.28 kB
text/typescript
import { Component } from '../Base/Component';
import { IComponentBindings } from '../Base/ComponentBindings';
import { ComponentOptions } from '../Base/ComponentOptions';
import { Assert } from '../../misc/Assert';
import { QueryEvents, IQuerySuccessEventArgs } from '../../events/QueryEvents';
import { ITriggerNotify, ITriggerExecute, ITriggerRedirect, ITriggerQuery, ITrigger } from '../../rest/Trigger';
import { $$ } from '../../utils/Dom';
import {
IAnalyticsTriggerNotify,
analyticsActionCauseList,
IAnalyticsTriggerRedirect,
IAnalyticsTriggerQuery,
IAnalyticsTriggerExecute
} from '../Analytics/AnalyticsActionListMeta';
import { QueryStateModel } from '../../models/QueryStateModel';
import { Initialization } from '../Base/Initialization';
import { object, map } from 'underscore';
import { exportGlobally } from '../../GlobalExports';
import 'styling/_Triggers';
import { IQuery } from '../../rest/Query';
import { Utils } from '../../UtilsModules';
export interface ITriggersOptions {}
function isTriggerNotify(trigger: ITrigger<any>): trigger is ITriggerNotify {
return trigger.type === 'notify';
}
function isTriggerRedirect(trigger: ITrigger<any>): trigger is ITriggerRedirect {
return trigger.type === 'redirect';
}
function isTriggerQuery(trigger: ITrigger<any>): trigger is ITriggerQuery {
return trigger.type === 'query';
}
function isTriggerExecute(trigger: ITrigger<any>): trigger is ITriggerExecute {
return trigger.type === 'execute';
}
/**
* The Triggers component enables the use of triggers (`notify`, `execute`, `query`, `redirect`) generated by the Coveo
* Search API (see [Trigger](https://docs.coveo.com/en/1458/)) in the query pipeline (see
* [Managing the Query Pipeline](https://docs.coveo.com/en/1450/)).
*
* To enable the triggers functionality in your JavaScript Search Framework interface, incorporate a div with the `coveoTriggers` class. For example: `<div class="coveoTriggers"></div>`.
*
* Note: adding the Triggers component gives query pipeline administrators the power to influence users' search experience.
* Bad actors will be able to perform XSS attacks, or redirect users to dangerous sites. Make sure only individuals you trust
* have query pipeline edit privileges.
*/
export class Triggers extends Component {
static ID = 'Triggers';
static options: ITriggersOptions = {};
static doExport = () => {
exportGlobally({
Triggers: Triggers
});
};
/**
* The list of notifications returned by the Search API for the current query (via `notify` triggers).
*
* The Triggers component automatically renders this list visually.
*/
public notifications: string[];
/**
* Creates a new Triggers component.
* @param element The HTMLElement on which to instantiate the component.
* @param options The options for the Triggers 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).
* @param _window The window on which to execute the triggers.
*/
constructor(
public element: HTMLElement,
public options?: ITriggersOptions,
public bindings?: IComponentBindings,
public _window?: Window
) {
super(element, Triggers.ID, bindings);
this._window = this._window || window;
this.options = ComponentOptions.initComponentOptions(element, Triggers, options);
Assert.exists(element);
Assert.exists(this.options);
this.notifications = [];
this.bind.onRootElement(QueryEvents.querySuccess, this.handleProcessNewQueryResults);
}
private handleProcessNewQueryResults(data: IQuerySuccessEventArgs) {
Assert.exists(data);
Assert.exists(data.results);
$$(this.element).empty();
this.notifications = [];
if (Utils.isNullOrUndefined(data.results.triggers)) {
$$(this.element).removeClass('coveo-visible');
return;
}
const notifyTriggers: ITriggerNotify[] = [];
let redirectTrigger: ITriggerRedirect | null = null;
let queryTrigger: ITriggerQuery | null = null;
let executeTrigger: ITriggerExecute | null = null;
data.results.triggers.forEach(trigger => {
if (isTriggerNotify(trigger)) {
notifyTriggers.push(trigger);
} else if (isTriggerRedirect(trigger)) {
redirectTrigger = redirectTrigger || trigger;
} else if (isTriggerQuery(trigger)) {
queryTrigger = queryTrigger || trigger;
} else if (isTriggerExecute(trigger)) {
executeTrigger = executeTrigger || trigger;
}
});
if (notifyTriggers.length) {
this.processNotifyTriggers(notifyTriggers);
$$(this.element).addClass('coveo-visible');
}
redirectTrigger && this.processRedirectTrigger(redirectTrigger);
queryTrigger && this.processQueryTrigger(queryTrigger);
executeTrigger && this.processExecuteTrigger(executeTrigger, data.query);
}
private processNotifyTriggers(triggers: ITriggerNotify[]) {
this.usageAnalytics.logCustomEvent<IAnalyticsTriggerNotify>(
analyticsActionCauseList.triggerNotify,
{
notifications: triggers.map(trigger => trigger.content)
},
this.element
);
triggers.forEach(trigger => {
this.notifications.push(trigger.content);
this.element.appendChild($$('div', { className: 'coveo-trigger-notify' }, trigger.content).el);
});
}
private processRedirectTrigger(trigger: ITriggerRedirect) {
this.usageAnalytics.logCustomEvent<IAnalyticsTriggerRedirect>(
analyticsActionCauseList.triggerRedirect,
{
redirectedTo: trigger.content
},
this.element
);
this._window.location.replace(trigger.content);
}
private processQueryTrigger(trigger: ITriggerQuery) {
this.queryStateModel.set(QueryStateModel.attributesEnum.q, trigger.content);
this.queryController.executeQuery({
beforeExecuteQuery: () => {
this.usageAnalytics.logCustomEvent<IAnalyticsTriggerQuery>(
analyticsActionCauseList.triggerQuery,
{
query: trigger.content
},
this.element
);
}
});
}
private processExecuteTrigger(trigger: ITriggerExecute, query: IQuery) {
try {
let func: Function = this._window['' + trigger.content.name];
if (typeof func === 'function') {
let params = object(
map(trigger.content.params, (value, index) => {
return ['param' + (index + 1), value];
})
);
params['element'] = this.element;
this.usageAnalytics.logCustomEvent<IAnalyticsTriggerExecute>(
analyticsActionCauseList.triggerExecute,
{
executions: [{ functionName: trigger.content.name, params: trigger.content.params }]
},
this.element
);
func.apply(this._window, [params]);
} else {
this.logger.error(`A trigger tried to call the function '${trigger.content.name}', which doesn't exist.`, this, query, trigger);
}
} catch (error) {
this.logger.error(`A trigger called the function '${trigger.content.name}', which threw an error.`, this, query, trigger);
}
}
}
Initialization.registerAutoCreateComponent(Triggers);