(function() {
/**
* @typedef {Help4.control2.bubble.content.BubbleContent.Params} Help4.control2.bubble.content.Panel.Params
* @property {?string} [caption = null] - caption
* @property {boolean} [showCaption = false] - whether to show caption
* @property {boolean} [showSearch = false] - whether to show search
* @property {boolean} [showTiles = false] - whether to show the widget tiles
* @property {boolean} [showContentArea = false] - whether to show the content area
* @property {?string} [brandingLogoSrc = null] - image source URL to possible branding logo
* @property {?string} [brandingLogoUrl = null] - navigation link to possible branding logo
*/
/**
* SAP Companion 4 panel content.
* @augments Help4.control2.bubble.content.BubbleContent
* @property {?string} caption - caption
* @property {boolean} showCaption - whether to show caption
* @property {boolean} showSearch - whether to show search
* @property {string} searchTerm - the search term
* @property {boolean} showTiles - whether to show the widget tiles
* @property {boolean} showContentArea - whether to show the content area
* @property {?string} brandingLogoSrc - image source URL to possible branding logo
* @property {?string} brandingLogoUrl - navigation link to possible branding logo
* @property {string} __usedContentArea - defines what content area is used
* @property {?Help4.control2.Text} _caption
* @property {?Help4.control2.container.PanelSearch} _panelSearch
* @property {?Help4.control2.container.PanelWidgetContainer} _panelWidgetContainer
* @property {?HTMLDivElement} _contentDiv
* @property {?HTMLIFrameElement} _sameOriginContent
* @property {?HTMLIFrameElement} _crossOriginContent
*/
Help4.control2.bubble.content.Panel = class extends Help4.control2.bubble.content.BubbleContent {
/**
* @override
* @param {Help4.control2.bubble.content.Panel.Params} [params]
*/
constructor(params) {
const {
TYPES: T,
TEXT_TYPES: TT
} = Help4.jscore.ControlBase;
super(params, {
params: {
// all defaults are handled by bubble.Panel; do not handle here!
// keep in sync with bubble.Panel!
caption: {type: T.string_null},
showCaption: {type: T.boolean},
showSearch: {type: T.boolean},
searchTerm: {type: T.string},
showTiles: {type: T.boolean},
showContentArea: {type: T.boolean},
brandingLogoSrc: {type: T.string_null, readonly: true},
brandingLogoUrl: {type: T.string_null, readonly: true},
role: {init: 'main'},
usedContentArea: {type: T.string, private: true}
},
statics: {
_caption: {},
_panelSearch: {},
_panelWidgetContainer: {},
_contentDiv: {destroy: false},
_sameOriginContent: {destroy: false},
_crossOriginContent: {destroy: false}
},
config: {
css: 'control-bubble-content-panel'
},
texts: {
brandingLogoTitle: TT.title,
brandingLogoAlt: TT.alt
}
});
}
/**
* @returns {HTMLDivElement}
*/
useContentDiv() {
this.__usedContentArea = 'contentDiv';
return this._contentDiv;
}
/**
* @returns {HTMLIFrameElement}
*/
useInternalIframe() {
this.__usedContentArea = 'internalIframe';
return this._sameOriginContent;
}
/**
* @returns {HTMLIFrameElement}
*/
useExternalIframe() {
this.__usedContentArea = 'externalIframe';
return this._crossOriginContent;
}
/**
* @override
* @returns {Help4.control2.bubble.content.Panel}
*/
focus() {
const {showSearch, _panelSearch, showTiles, _panelWidgetContainer} = this;
showSearch
? _panelSearch.focus() // focus on search bar input
: showTiles && !!_panelWidgetContainer?.count()
? _panelWidgetContainer.get(0).focus() // we are in home screen and focus the first widget tile, if available
: super.focus(); // focus content area instead
return this;
}
/**
* focus handling - up/down/left/right arrow keys
* @param {string} direction
*/
focusListItem(direction) {
const {showTiles, _panelWidgetContainer} = this;
const count = _panelWidgetContainer?.count() || 0;
if (!showTiles || count <= 1) return;
const visibleWidgets = [];
_panelWidgetContainer.forEach(widgetControl => widgetControl.visible && visibleWidgets.push(widgetControl));
const focussedElement = /** @type {HTMLElement} */ Help4.widget.getActiveElement();
let index = visibleWidgets.findIndex(widgetControl => widgetControl.getDom() === focussedElement);
if (index < 0) return; // focussed control is not a widget
const {x, left} = focussedElement.getBoundingClientRect();
switch (direction) {
case 'up':
while(index > 0) {
const prevControl = visibleWidgets[index - 1];
const {x: px, left: pleft} = prevControl.getDom().getBoundingClientRect();
if (x === px && left === pleft) {
prevControl.focus();
break;
} else {
index--;
}
}
break;
case 'down':
while(index < count - 1) {
const nextControl = visibleWidgets[index + 1];
const {x: nx, left: nleft} = nextControl.getDom().getBoundingClientRect();
if (x === nx && left === nleft) {
nextControl.focus();
break;
} else {
index++;
}
}
break;
case 'left':
visibleWidgets[index - 1]?.focus();
break;
case 'right':
visibleWidgets[index + 1]?.focus();
break;
}
}
/**
* @override
* @param {Help4.control2.bubble.content.Panel.Params} params - same params as provided to the constructor
*/
_onAfterInit(params) {
super._onAfterInit(params);
// remove "dgo_text_container" class as not applicable for panel
// this.css is readonly and cannot be changed
// therefore use specific readonly override handling from Help4.jscore.ControlBase
this.dataFunctions.set({css: this.css.replace(/dgo_text_container/, '')}, {allowReadonlyOverride: true});
}
/**
* @override
* @param {HTMLElement} dom - control DOM
*/
_onDomAvailable(dom) {
super._onDomAvailable(dom);
const {id, __bubble, brandingLogoSrc, brandingLogoUrl} = this;
const {control2, Element, Localization} = Help4;
this._caption = this._createControl(control2.Text, {
text: this.caption,
id: `${id}-caption`,
dom,
tag: 'h3',
css: 'caption'
});
const ps = this._panelSearch = this._createControl(control2.container.PanelSearch, {
id: `${id}-panelsearch`,
dom
})
.addListener('search', event => void this._fireEvent(event));
ps.dataFunctions.onChange.addListener(({name, value}) => (name === 'value') && (this.searchTerm = value));
this._panelWidgetContainer = this._createControl(control2.container.PanelWidgetContainer, {
id: `${id}-panelwidgets`,
dom,
panel: __bubble,
role: 'list',
ariaRoleDescription: Localization.getText('description.widget.list')
})
.addListener('widget', event => void this._fireEvent(event));
const document = this.getDocument();
if (brandingLogoSrc && brandingLogoUrl) {
const title = Localization.getText('tooltip.carousel.logourl');
const brandingLink = Element.create('A', {
id: `${id}-branding-logo-anchor`,
dom,
document,
css: 'logo-link',
href: brandingLogoUrl,
title,
ariaLabel: title,
onclick: event => {
event.preventDefault(); // prevented navigation to securely open the link in a new window
Help4.windowOpen(brandingLogoUrl);
}
});
this._setTextAttribute(brandingLink, 'brandingLogoTitle');
const brandingImg = Element.create('IMG', {
id: `${id}-branding-logo-image`,
dom: brandingLink,
document,
css: 'logo-image',
src: brandingLogoSrc,
role: 'presentation',
alt: Localization.getText('tooltip.carousel.logosrc')
});
this._setTextAttribute(brandingImg, 'brandingLogoAlt');
}
this._contentDiv = Element.create('DIV', {
dom,
document,
css: 'widget-content hidden'
});
this._sameOriginContent = Element.create('IFRAME', {
dom,
document,
css: 'widget-content same-origin hidden'
});
this._crossOriginContent = Element.create('IFRAME', {
dom,
document,
css: 'widget-content cross-origin hidden'
});
}
/**
* @override
* @param {Help4.jscore.ControlBase.PropertyChangeEvent} event - the change event
*/
_applyPropertyToDom({name, value, oldValue}) {
switch (name) {
case 'caption':
this._caption.text = value;
break;
case 'showCaption':
this._caption.visible = value;
break;
case 'searchTerm':
this._panelSearch.value = value;
break;
case 'showSearch':
this._panelSearch.visible = value;
break;
case 'showTiles':
case 'showContentArea':
_handleVisibility.call(this, name);
break;
case 'usedContentArea':
_handleVisibility.call(this, 'showContentArea');
break;
default:
super._applyPropertyToDom({name, value, oldValue});
break;
}
}
}
/**
* @memberof Help4.control2.bubble.content.Panel#
* @param {'showTiles'|'showContentArea'} propertyName
* @private
*/
function _handleVisibility(propertyName) {
const controls = {
tiles: this._panelWidgetContainer,
contentDiv: this._contentDiv,
internalIframe: this._sameOriginContent,
externalIframe: this._crossOriginContent
};
const visible = this[propertyName];
const {__bubble, __usedContentArea} = this;
const show = (elem, visible) => {
elem instanceof Help4.control2.Control
? elem.visible = visible
: Help4.Element[visible ? 'removeClass' : 'addClass'](elem, 'hidden'); // DOM Node
}
if (propertyName === 'showTiles') {
// my properties are just mirroring the ones of my bubble
// update the bubble instead of myself;
// bubble will propagate to me if needed
if (visible) __bubble.showContentArea = false;
show(controls.tiles, visible);
} else { // showContentArea
if (visible && __usedContentArea) __bubble.showTiles = false;
for (const key of ['contentDiv', 'internalIframe', 'externalIframe']) {
show (controls[key], visible && __usedContentArea === key);
}
}
}
})();