(function() {
/**
* @namespace filter
* @memberof Help4.widget
*/
Help4.widget.filter = {};
const NAME = 'filter';
/**
* Filter functionality widget
* @augments Help4.widget.Widget
* @property {?string} _term
* @property {Help4.control2.container.Container} _container
*/
Help4.widget.filter.Widget = class extends Help4.widget.Widget {
/** @override */
constructor() {
const onDomRefresh = () => _checkContentRefresh.call(this);
super({
params: {
visible: {init: true}
},
statics: {
_term: {destroy: false},
_container: {},
_results: {destroy: false},
_onDomRefresh: {init: onDomRefresh, destroy: false}
}
});
}
/** @override */
getName() {
return NAME;
}
/**
* @override
* @returns {Promise<Help4.widget.Widget.Descriptor>}
*/
async _onGetDescriptor() {
return {
id: NAME,
enabled: true,
showPanel: true,
tile: {
title: Help4.Localization.getText('button.widget.filter'),
showSearch: true,
visible: false,
}
};
}
/**
* set fulltext term and sync panel and widget
* @param {string} term
* @returns {Promise<void>}
*/
async execSearch(term) {
// note: as we change the value in the panel it will automatically call setTerm
// to synchronize with this widget (i.e term and this._term are always in sync)
_setPanelTerm.call(this, term.toLowerCase());
if (this.isActive()) {
term
? await _fillContainer.call(this) // refresh content if widget is active
: _cleanContainer.call(this); // empty search results on empty term
} else {
await this.activate();
}
this.focus();
}
/** @override */
focus() {
const {panel} = this.getContext();
panel?.visible && panel.focus();
}
/**
* focus handling - up/down arrow keys
* @param {string} direction
*/
focusListItem(direction) {
const {_container} = this;
const count = _container.count();
if (count === 0) return;
const visibleControls = [];
_container.forEach(control => {
if (control instanceof Help4.control2.container.Container) {
control.forEach(tile => tile.visible && visibleControls.push(tile));
}
});
const focussedElement = Help4.widget.getActiveElement();
let index = visibleControls.findIndex(control => control.getDom() === focussedElement);
if (index >= 0) visibleControls[direction === 'down' ? ++index : --index]?.focus();
}
/**
* set fulltext search term for internal use
* @param {string} term
*/
setTerm(term) {
// always in sync with panel.searchTerm
if (this._term === term) return;
this._term = term.toLowerCase();
}
/**
* @override
* @returns {Promise<Object>}
*/
async getTexts() {
if (!this.isActive()) return null;
const {panel} = this.getContext();
return panel?.getTexts() || {};
}
/**
* @override
* @param {Object} texts
* @returns {Promise<void>}
*/
async setTexts(texts) {
const {panel} = this.getContext();
panel?.setTexts(texts);
}
/**
* @override
* @returns {Promise<boolean|void>}
*/
async _onBeforeActivate() {
if (!this._term) return false; // block widget activation w/o search term
}
/**
* @override
* @param {{isReturn: boolean}|null} [data = null]
* @returns {Promise<void>}
*/
async _onAfterActivate(data = null) {
const {
configuration: {core: {rtl, mobile, language: {_: language}}}
} = this.getContext();
const {Container} = Help4.control2.container;
const {panel} = this.getContext();
const dom = panel?.getContentInstance()?.useContentDiv();
this._container = new Container({rtl, mobile, language, id: 'widget-filter-result', contentLanguage: language, dom})
.addListener(['click', 'space', 'enter'], event => _onEvent.call(this, event));
await _fillContainer.call(this, data);
if (this.isDestroyed()) return;
_handleMonitor.call(this, true);
}
/**
* @override
* @returns {Promise<void>}
*/
async _onAfterDeactivate() {
this._destroyControl('_container');
_handleMonitor.call(this, false);
}
/**
* @override
* @returns {Promise<void>}
*/
async _onSystemNavigate() {
_setPanelTerm.call(this);
await this.deactivate();
}
/**
* @override
* @returns {Promise<void>}
*/
async _onControllerClose() {
_setPanelTerm.call(this);
await this.deactivate();
}
}
/**
* @memberof Help4.widget.filter.Widget#
* @param {string} [term = '']
* @private
*/
function _setPanelTerm(term = '') {
const {panel} = this.getContext();
panel && panel.searchTerm !== term && (panel.searchTerm = term);
}
/**
* @memberof Help4.widget.filter.Widget#
* @private
*/
function _cleanContainer() {
this._container?.clean();
}
/**
* @memberof Help4.widget.filter.Widget#
* @param {{isReturn: boolean}|null} [data = null]
* @returns {Promise<void>}
* @private
*/
async function _fillContainer(data = null) {
const {isReturn = false} = data || {};
const {_term: fulltext} = this;
const results = this._results = await Help4.widget.awaitAll(instance => instance.filter({fulltext}));
if (this.isDestroyed()) return;
_cleanContainer.call(this);
const {Localization} = Help4;
const {_container} = this;
if (!_container) return;
isReturn || _track.call(this, fulltext); // only track if new search and not return to search; XRAY-6331
for (const [name, /** @type {Help4.widget.Widget.SearchResult[]} */ hits] of Object.entries(results)) {
if (!hits.length) continue;
// section header
_container.add({
controlType: 'Text',
tag: 'h3',
css: 'caption widget',
text: Localization.getText(`header.filter.${name}`),
});
const {Placeholder} = Help4;
const content = _container.add({controlType: 'container.Container', type: 'Tile'});
for (/** @type {Help4.widget.Widget.SearchResult} */ const hit of hits) {
switch (name) {
case 'tourlist': {
const {caption, projectId, catalogueKey, catalogueType, dataType, whatsnew, contentLanguage} = hit;
content.add({
_metadata: {
type: 'tour',
data: /** @type {Help4.widget.tour.Widget.StartStatus} */ {
projectId,
catalogueKey,
catalogueType,
dataType,
whatsnew: !!whatsnew
}
},
caption: Placeholder.resolve(caption),
contentLanguage,
css: 'tour-project',
});
break;
}
case 'learning': {
const {caption, description, entityType, entitySubType, entityUid, contentLanguage} = hit;
content.add({
controlType: 'Help4.widget.learning.TileControl',
_metadata: {
type: 'learning',
data: {entityUid, entityType, entitySubType, caption}
},
caption,
description,
entityType,
entitySubType,
enableFeedback: false,
contentLanguage
})
break;
}
case 'help': {
const {caption, description, type, projectId, tileId, catalogueKey, catalogueType, dataType, icon, showAsButton, contentLanguage} = hit;
content.add({
_metadata: {
type: 'help',
subtype: type,
data: {projectId, tileId, catalogueKey, catalogueType, dataType}
},
caption: Placeholder.resolve(caption),
description: Placeholder.resolve(description),
css: `${type}-tile`,
icon: showAsButton ? null : icon,
contentLanguage
});
break;
}
case 'whatsnew': {
const {caption, description, type, projectId, tileId, catalogueKey, catalogueType, dataType, icon, showAsButton, contentLanguage} = hit;
content.add({
_metadata: {
type: 'whatsnew',
subtype: type,
data: {projectId, tileId, catalogueKey, catalogueType, dataType, whatsnew: true}
},
caption: Placeholder.resolve(caption),
description: Placeholder.resolve(description),
css: type === 'tour' ? 'tour-project' : `${type}-tile`,
icon: showAsButton ? null : icon,
contentLanguage
});
break;
}
}
}
}
}
/**
* @memberof Help4.widget.filter.Widget#
* @param {{target: Help4.control2.Control[]}} event
* @returns {Promise<void>}
* @private
*/
async function _onEvent({target}) {
const [container1, container2, tile] = target;
const {type, subtype, data} = tile?.getMetadata('type', 'subtype', 'data') || {};
const {Widget: TourlistWidget} = Help4.widget.tourlist;
switch (type) {
case 'tour':
const {configuration} = this.getContext();
await this.deactivate({tour: true});
await TourlistWidget.startTour(configuration, data);
break;
case 'learning':
const learningWidget = Help4.widget.getInstance('learning');
Help4.widget.learning.View.openLearning(learningWidget, data); // always ex-place
break;
case 'whatsnew':
if (subtype === 'tour') {
const {configuration} = this.getContext();
await this.deactivate({tour: true});
await TourlistWidget.startTour(configuration, data);
} else {
const widget = /** @type {Help4.widget.whatsnew.Widget} */ Help4.widget.getInstance('whatsnew');
await this.deactivate({help: true});
await widget.startFromFilter(data);
}
break;
case 'help':
const widget = /** @type {Help4.widget.help.Widget} */ Help4.widget.getInstance('help');
await this.deactivate({help: true});
await widget.startFromFilter(data);
break;
}
}
/**
* @memberof Help4.widget.filter.Widget#
* @param {string} term
* @private
*/
function _track(term) {
const {container: {Container}, Tile} = Help4.control2;
const calcCount = container => {
let nbr = 0;
for (const item of container) {
if (item instanceof Container) {
nbr += calcCount(item);
} else if (item instanceof Tile) {
nbr++;
}
}
return nbr;
}
const {controller} = this.getContext();
const tracking = controller.getService('tracking');
tracking?.trackProject({
verb: 'search',
type: 'help',
term,
getResults: () => this._container ? calcCount(this._container) : 0
});
}
/**
* @memberof Help4.widget.filter.Widget#
* @param {boolean} start
* @private
*/
function _handleMonitor(start) {
const {Core} = Help4.widget.companionCore;
const context = this.getContext();
start
? Core.observeDomRefresh(this._onDomRefresh, context, this)
: Core.disconnectDomRefresh(this._onDomRefresh, context);
}
/**
* @memberof Help4.widget.filter.Widget#
* @private
* @returns {Promise<void>}
*/
async function _checkContentRefresh() {
const {_term: fulltext, _results} = this;
const results = await Help4.widget.awaitAll(instance => instance.filter({fulltext}));
if (!this.isDestroyed() && !Help4.equalObjects(results, _results)) {
await _fillContainer.call(this, {isReturn: true});
}
}
})();