UNPKG

coveo-search-ui

Version:

Coveo JavaScript Search Framework

314 lines (275 loc) • 11 kB
import 'styling/_FieldTable'; import { each, uniqueId } from 'underscore'; import { exportGlobally } from '../../GlobalExports'; import { IQueryResult } from '../../rest/QueryResult'; import { l } from '../../strings/Strings'; import { AccessibleButton } from '../../utils/AccessibleButton'; import { $$ } from '../../utils/Dom'; import { QueryUtils } from '../../utils/QueryUtils'; import { SVGDom } from '../../utils/SVGDom'; import { SVGIcons } from '../../utils/SVGIcons'; import { Component } from '../Base/Component'; import { IComponentBindings } from '../Base/ComponentBindings'; import { ComponentOptions } from '../Base/ComponentOptions'; import { Initialization } from '../Base/Initialization'; import { FieldValue, IFieldValueOptions } from '../FieldValue/FieldValue'; export interface IFieldTableOptions { allowMinimization: boolean; expandedTitle: string; minimizedTitle: string; minimizedByDefault: boolean; } /** * The FieldTable component displays a set of {@link FieldValue} components in a table that can optionally be * expandable and minimizable. This component automatically takes care of not displaying empty field values. * * This component is a result template component (see [Result Templates](https://docs.coveo.com/en/413/)). * * **Example:** * * ``` * // This is the FieldTable component itself, which holds a list of table rows. * // Each row is a FieldValue component. * <table class='CoveoFieldTable'> * // Items * <tr data-field='@sysdate' data-caption='Date' data-helper='dateTime' /> * <tr data-field='@sysauthor' data-caption='Author' /> * <tr data-field='@clickuri' data-html-value='true' data-caption='URL' data-helper='anchor' data-helper-options='{text: \"<%= raw.syssource %>\" , target:\"_blank\"}'> * </table> * ``` */ export class FieldTable extends Component { static ID = 'FieldTable'; static doExport = () => { exportGlobally({ FieldTable: FieldTable, FieldValue: FieldValue }); }; /** * The options for the component * @componentOptions */ static options: IFieldTableOptions = { /** * Specifies whether to allow the minimization (collapsing) of the FieldTable. * * If you set this option to `false`, the component will not create the **Minimize** / **Expand** toggle links. * * See also {@link FieldTable.options.expandedTitle}, {@link FieldTable.options.minimizedTitle} and * {@link FieldTable.options.minimizedByDefault}. * * Default value is `true`. */ allowMinimization: ComponentOptions.buildBooleanOption({ defaultValue: true }), /** * If {@link FieldTable.options.allowMinimization} is `true`, specifies the caption to show on the **Minimize** link * (the link that appears when the FieldTable is expanded). * * Default value is `"Details"`. */ expandedTitle: ComponentOptions.buildLocalizedStringOption({ localizedString: () => l('Details'), depend: 'allowMinimization' }), /** * If {@link FieldTable.options.allowMinimization} is `true`, specifies the caption to show on the **Expand** link * (the link that appears when the FieldTable is minimized). * * Default value is `"Details"`. */ minimizedTitle: ComponentOptions.buildLocalizedStringOption({ localizedString: () => l('Details'), depend: 'allowMinimization' }), /** * If {@link FieldTable.options.allowMinimization} is `true`, specifies whether to minimize the table by default. * * Default value is `undefined`, and the FieldTable will collapse by default if the result it is associated with has * a non-empty excerpt. */ minimizedByDefault: ComponentOptions.buildBooleanOption({ depend: 'allowMinimization' }) }; private toggleButton: HTMLElement; private toggleCaption: HTMLElement; private toggleButtonSVGContainer: HTMLElement; private toggleButtonInsideTable: HTMLElement; private toggleContainer: HTMLElement; private toggleContainerHeight: number; /** * Creates a new FieldTable. * @param element The HTMLElement on which to instantiate the component. * @param options The options for the FieldTable 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?: IFieldTableOptions, bindings?: IComponentBindings, public result?: IQueryResult ) { super(element, ValueRow.ID, bindings); this.options = ComponentOptions.initComponentOptions(element, FieldTable, options); var rows = $$(this.element).findAll('tr[data-field]'); each(rows, (e: HTMLElement) => { new ValueRow(e, {}, bindings, result); }); if ($$(this.element).find('tr') == null) { $$(element).detach(); } if (this.isTogglable()) { const className = 'coveo-field-table-toggle-container'; this.toggleContainer = $$('div', { className, id: uniqueId(className) }).el; this.buildToggle(); $$(this.toggleContainer).insertBefore(this.element); this.toggleContainer.appendChild(this.element); this.toggleContainer.appendChild(this.toggleButtonInsideTable); } } public get isExpanded() { return !this.toggleButton || this.toggleButton.getAttribute('aria-expanded') === 'true'; } public set isExpanded(expanded: boolean) { if (this.toggleButton) { this.toggleButton.setAttribute('aria-expanded', expanded.toString()); } } /** * Toggles between expanding (showing) and minimizing (collapsing) the FieldTable. * * @param anim Specifies whether to show a sliding animation when toggling the display of the FieldTable. */ public toggle(anim = false) { this.isExpanded ? this.minimize(anim) : this.expand(anim); } /** * Expands (shows) the FieldTable, * @param anim Specifies whether to show a sliding animation when expanding the FieldTable. */ public expand(anim = false) { if (this.isTogglable()) { this.isExpanded = true; this.toggleCaption.textContent = this.options.expandedTitle; SVGDom.addClassToSVGInContainer(this.toggleButtonSVGContainer, 'coveo-opened'); SVGDom.addClassToSVGInContainer(this.toggleButtonInsideTable, 'coveo-opened'); anim ? this.slideToggle(true) : this.slideToggle(true, false); } } /** * Minimizes (collapses) the FieldTable. * @param anim Specifies whether to show a sliding animation when minimizing the FieldTable. */ public minimize(anim = false) { if (this.isTogglable()) { this.isExpanded = false; this.toggleCaption.textContent = this.options.minimizedTitle; SVGDom.removeClassFromSVGInContainer(this.toggleButtonSVGContainer, 'coveo-opened'); SVGDom.removeClassFromSVGInContainer(this.toggleButtonInsideTable, 'coveo-opened'); anim ? this.slideToggle(false) : this.slideToggle(false, false); } } /** * Updates the toggle height if the content was dynamically resized, so that the expanding and minimizing animation * can match the new content size. */ public updateToggleHeight() { this.updateToggleContainerHeight(); this.isExpanded ? this.expand() : this.minimize(); } protected isTogglable() { if (this.options.allowMinimization) { return true; } return false; } private buildToggle() { this.toggleCaption = $$('span', { className: 'coveo-field-table-toggle-caption' }).el; this.toggleButton = $$('div', { className: 'coveo-field-table-toggle coveo-field-table-toggle-down', ariaControls: this.toggleContainer.id }).el; this.toggleButtonSVGContainer = $$('span', null, SVGIcons.icons.arrowDown).el; SVGDom.addClassToSVGInContainer(this.toggleButtonSVGContainer, 'coveo-field-table-toggle-down-svg'); this.toggleButton.appendChild(this.toggleCaption); this.toggleButton.appendChild(this.toggleButtonSVGContainer); $$(this.toggleButton).insertBefore(this.element); this.toggleButtonInsideTable = $$( 'span', { className: 'coveo-field-table-toggle coveo-field-table-toggle-up' }, SVGIcons.icons.arrowUp ).el; SVGDom.addClassToSVGInContainer(this.toggleButtonInsideTable, 'coveo-field-table-toggle-up-svg'); if (this.options.minimizedByDefault === true) { this.isExpanded = false; } else if (this.options.minimizedByDefault === false) { this.isExpanded = true; } else { this.isExpanded = !QueryUtils.hasExcerpt(this.result); } requestAnimationFrame(() => this.updateToggleHeight()); const toggleAction = () => this.toggle(true); new AccessibleButton() .withElement(this.toggleButton) .withSelectAction(() => toggleAction()) .withOwner(this.bind) .withLabel(l('Details')) .build(); $$(this.toggleButtonInsideTable).on('click', toggleAction); } private slideToggle(visible: boolean = true, anim: boolean = true) { if (!anim) { $$(this.toggleContainer).addClass('coveo-no-transition'); } if (visible) { this.toggleContainer.style.display = 'block'; this.toggleContainer.style.height = this.containerHeight; } else { this.toggleContainer.style.height = '0'; } if (!anim) { this.toggleContainer.offsetHeight; // Force reflow $$(this.toggleContainer).removeClass('coveo-no-transition'); } } private get containerHeight() { if (!this.toggleContainerHeight) { this.updateToggleContainerHeight(); } return this.toggleContainerHeight + 'px'; } private updateToggleContainerHeight() { this.toggleContainerHeight = this.toggleContainer.scrollHeight; } } Initialization.registerAutoCreateComponent(FieldTable); export interface IValueRowOptions extends IFieldValueOptions { caption?: string; } class ValueRow extends FieldValue { static ID = 'ValueRow'; static options: IValueRowOptions = { caption: ComponentOptions.buildStringOption({ postProcessing: (value, options) => value || options.field.substr(1) }) }; static parent = FieldValue; private valueContainer: HTMLElement; constructor(public element: HTMLElement, public options: IValueRowOptions, bindings?: IComponentBindings, public result?: IQueryResult) { super(element, options, bindings, result, ValueRow.ID); this.options = ComponentOptions.initComponentOptions(element, ValueRow, options); var caption = $$('th').el; caption.appendChild(document.createTextNode(this.options.caption.toLocaleString())); this.element.insertBefore(caption, this.getValueContainer()); } protected getValueContainer() { if (this.valueContainer == null) { this.valueContainer = document.createElement('td'); this.element.appendChild(this.valueContainer); } return this.valueContainer; } }