UNPKG

coveo-search-ui

Version:

Coveo JavaScript Search Framework

284 lines (254 loc) • 10.5 kB
import * as _ from 'underscore'; import { ISearchAlertsEventArgs, ISearchAlertsFailEventArgs, SearchAlertsEvents } from '../../events/SearchAlertEvents'; import { exportGlobally } from '../../GlobalExports'; import { Assert } from '../../misc/Assert'; import { IQueryResult } from '../../rest/QueryResult'; import { ISubscription, ISubscriptionItemRequest, ISubscriptionRequest, SUBSCRIPTION_TYPE } from '../../rest/Subscription'; import { l } from '../../strings/Strings'; import { $$, Dom } from '../../utils/Dom'; import { KEYBOARD, KeyboardUtils } from '../../utils/KeyboardUtils'; import { QueryUtils } from '../../utils/QueryUtils'; import { SVGDom } from '../../utils/SVGDom'; import { SVGIcons } from '../../utils/SVGIcons'; import { Utils } from '../../utils/Utils'; import { analyticsActionCauseList, IAnalyticsActionCause, IAnalyticsSearchAlertsFollowDocumentMeta } from '../Analytics/AnalyticsActionListMeta'; import { Component } from '../Base/Component'; import { ComponentOptions } from '../Base/ComponentOptions'; import { IFieldOption } from '../Base/IComponentOptions'; import { Initialization } from '../Base/Initialization'; import { IResultsComponentBindings } from '../Base/ResultsComponentBindings'; export interface IFollowItemOptions { watchedFields?: IFieldOption[]; modifiedDateField?: string; } /** * The FollowItem component renders a widget that the end user can click to follow a particular item. A user following * an item receives email notifications when the item changes. * * **Note:** * > A {@link SearchAlerts} component must be present in the page for this component to work. It is also necessary to * > meet certain requirements to be able to use this component (see * > [Deploying Search Alerts on a Coveo JS Search Page](https://docs.coveo.com/en/1932/)). * * This component is a result template component (see [Result Templates](https://docs.coveo.com/en/413/)). */ export class FollowItem extends Component { static ID = 'FollowItem'; static doExport = () => { exportGlobally({ FollowItem: FollowItem }); }; /** * The options for the follow item component * @componentOptions */ static options: IFollowItemOptions = { /** * Specifies the {@link ISubscriptionItemRequest.watchedFields} to use when sending the * {@link ISubscriptionItemRequest}. * * Default value is `undefined`. */ watchedFields: ComponentOptions.buildFieldsOption(), /** * Specifies the {@link ISubscriptionItemRequest.modifiedDateField} to use when sending the * {@link ISubscriptionItemRequest}. * * Default value is `undefined`. */ modifiedDateField: ComponentOptions.buildStringOption() }; private container: Dom; private text: Dom; private subscription: ISubscription; /** * Creates a new FollowItem component. * @param element The HTMLElement on which to instantiate the component. * @param options The options for the FollowItem 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 result The result to associate the component with. */ constructor( public element: HTMLElement, public options?: IFollowItemOptions, public bindings?: IResultsComponentBindings, public result?: IQueryResult ) { super(element, FollowItem.ID, bindings); this.options = ComponentOptions.initComponentOptions(element, FollowItem, options); Assert.exists(this.result); this.container = $$(this.element); this.text = $$('span'); const icon = this.buildIcon(); const loadingIcon = this.buildLoadingIcon(); this.container.append(icon); this.container.append(loadingIcon); this.container.append(this.text.el); this.container.on('click', () => this.toggleFollow()); this.container.setAttribute('tabindex', '0'); this.bind.on(this.container, 'keyup', KeyboardUtils.keypressAction(KEYBOARD.ENTER, () => this.toggleFollow())); this.bind.onRootElement(SearchAlertsEvents.searchAlertsDeleted, (args: ISearchAlertsEventArgs) => this.handleSubscriptionDeleted(args)); this.bind.onRootElement(SearchAlertsEvents.searchAlertsCreated, (args: ISearchAlertsEventArgs) => this.handleSubscriptionCreated(args)); this.container.addClass('coveo-follow-item-loading'); this.updateIsFollowed(); } public setFollowed(subscription: ISubscription) { this.container.removeClass('coveo-follow-item-loading'); this.subscription = subscription; this.container.addClass('coveo-follow-item-followed'); this.text.text(l('SearchAlerts_unFollowing')); } public setNotFollowed() { this.container.removeClass('coveo-follow-item-loading'); this.subscription = <ISubscription>FollowItem.buildFollowRequest(this.getId(), this.result.title, this.options); this.container.removeClass('coveo-follow-item-followed'); this.text.text(l('SearchAlerts_follow')); } /** * Follows the item if not already following it. Stops following the item otherwise. * * Also logs the appropriate event in the usage analytics (either `searchAlertsFollowDocument` or * `searchAlertsUnfollowDocument`). */ public toggleFollow() { if (!this.container.hasClass('coveo-follow-item-loading')) { this.container.removeClass('coveo-follow-item-followed'); this.container.addClass('coveo-follow-item-loading'); if (this.subscription.id) { this.logAnalyticsEvent(analyticsActionCauseList.searchAlertsUnfollowDocument); this.queryController .getEndpoint() .deleteSubscription(this.subscription) .then(() => { const eventArgs: ISearchAlertsEventArgs = { subscription: this.subscription, dom: this.element }; $$(this.root).trigger(SearchAlertsEvents.searchAlertsDeleted, eventArgs); }) .catch(() => { this.container.removeClass('coveo-follow-item-loading'); const eventArgs: ISearchAlertsFailEventArgs = { dom: this.element }; $$(this.root).trigger(SearchAlertsEvents.searchAlertsFail, eventArgs); }); } else { this.logAnalyticsEvent(analyticsActionCauseList.searchAlertsFollowDocument); this.queryController .getEndpoint() .follow(this.subscription) .then((subscription: ISubscription) => { const eventArgs: ISearchAlertsEventArgs = { subscription: subscription, dom: this.element }; $$(this.root).trigger(SearchAlertsEvents.searchAlertsCreated, eventArgs); }) .catch(() => { this.container.removeClass('coveo-follow-item-loading'); const eventArgs: ISearchAlertsFailEventArgs = { dom: this.element }; $$(this.root).trigger(SearchAlertsEvents.searchAlertsFail, eventArgs); }); } } } protected getText(): string { return this.text.text(); } private updateIsFollowed() { this.queryController .getEndpoint() .listSubscriptions() .then((subscriptions: ISubscription[]) => { if (_.isArray(subscriptions)) { const subscription: ISubscription = _.find(subscriptions, (subscription: ISubscription) => { const typeConfig = <ISubscriptionItemRequest>subscription.typeConfig; return typeConfig && typeConfig.id != null && typeConfig.id == this.getId(); }); if (subscription != null) { this.setFollowed(subscription); } else { this.setNotFollowed(); } } else { this.remove(); } }) .catch(() => { this.remove(); }); } private buildIcon(): HTMLElement { const icon = $$('span', { className: 'coveo-follow-item-icon' }, SVGIcons.icons.dropdownFollowQuery); SVGDom.addClassToSVGInContainer(icon.el, 'coveo-follow-item-icon-svg'); return icon.el; } private buildLoadingIcon(): HTMLElement { const loadingIcon = $$('span', { className: 'coveo-follow-item-icon-loading' }, SVGIcons.icons.loading); SVGDom.addClassToSVGInContainer(loadingIcon.el, 'coveo-follow-item-icon-loading-svg'); return loadingIcon.el; } private handleSubscriptionDeleted(args: ISearchAlertsEventArgs) { if (args.subscription && args.subscription.type == SUBSCRIPTION_TYPE.followDocument) { const typeConfig = <ISubscriptionItemRequest>args.subscription.typeConfig; if (typeConfig.id == this.getId()) { this.setNotFollowed(); } } } private handleSubscriptionCreated(args: ISearchAlertsEventArgs) { if (args.subscription && args.subscription.type == SUBSCRIPTION_TYPE.followDocument) { const typeConfig = <ISubscriptionItemRequest>args.subscription.typeConfig; if (typeConfig.id == this.getId()) { this.setFollowed(args.subscription); } } } private remove() { this.element.parentElement && this.element.parentElement.removeChild(this.element); } private getId() { return Utils.getFieldValue(this.result, 'sysurihash') || Utils.getFieldValue(this.result, 'urihash'); } private static buildFollowRequest(id: string, title: string, options: IFollowItemOptions): ISubscriptionRequest { const typeCofig: ISubscriptionItemRequest = { id: id, title: title }; if (options.modifiedDateField) { typeCofig.modifiedDateField = options.modifiedDateField; } if (options.watchedFields) { typeCofig.watchedFields = <string[]>options.watchedFields; } return { type: SUBSCRIPTION_TYPE.followDocument, typeConfig: typeCofig, name: title }; } private logAnalyticsEvent(type: IAnalyticsActionCause) { this.usageAnalytics.logCustomEvent<IAnalyticsSearchAlertsFollowDocumentMeta>( type, { author: QueryUtils.getAuthor(this.result), documentLanguage: QueryUtils.getLanguage(this.result), documentSource: QueryUtils.getSource(this.result), documentTitle: this.result.title, contentIDValue: QueryUtils.getPermanentId(this.result).fieldValue, contentIDKey: QueryUtils.getPermanentId(this.result).fieldUsed }, this.element ); } } Initialization.registerAutoCreateComponent(FollowItem);