Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

addNodeParams, getNodeParams and setAnchor for wc luigiClient #3389

Merged
merged 23 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
408fce6
client api change signature of renderWebComponent
JohannesDoberer Aug 7, 2023
ebfe99d
fix
JohannesDoberer Aug 7, 2023
6a4f836
ts declaration, context for compound
JohannesDoberer Aug 7, 2023
3a3fa42
Merge branch 'main' into extend-LuiguClientAPI-for-wc
JohannesDoberer Aug 7, 2023
2a79739
restrictedAPI
JohannesDoberer Aug 8, 2023
ae7bcc9
check isCompoundChild, desanitize getNodeParams
JohannesDoberer Aug 9, 2023
5cfda16
fix runtime err
JohannesDoberer Aug 9, 2023
0f1ed41
getNodeParams Luigicontainer
JohannesDoberer Aug 9, 2023
e89bda1
normalizeUrl, de/sanitize
JohannesDoberer Aug 10, 2023
1702755
test core wc luigi client
JohannesDoberer Aug 10, 2023
fd4202b
Merge branch 'main' into extend-LuiguClientAPI-for-wc
JohannesDoberer Aug 11, 2023
3a85815
add/getNodeParams fix
JohannesDoberer Aug 14, 2023
82f6e4f
runtime err fix
JohannesDoberer Aug 14, 2023
fa131ac
Update container/public/LuigiContainer.svelte.d.ts
JohannesDoberer Aug 14, 2023
5c8d446
no api for special
JohannesDoberer Aug 14, 2023
308a234
renaming
JohannesDoberer Aug 14, 2023
9b40a76
container anchor
JohannesDoberer Aug 16, 2023
5d13597
Merge branch 'main' into extend-LuiguClientAPI-for-wc
JohannesDoberer Aug 16, 2023
cd6bc11
Merge branch 'main' into extend-LuiguClientAPI-for-wc
JohannesDoberer Aug 16, 2023
95bcd98
Merge branch 'main' into extend-LuiguClientAPI-for-wc
JohannesDoberer Aug 16, 2023
5624a2c
Merge branch 'main' into extend-LuiguClientAPI-for-wc
JohannesDoberer Aug 16, 2023
4ca5d94
Merge branch 'main' into extend-LuiguClientAPI-for-wc
JohannesDoberer Aug 17, 2023
10320dc
Merge branch 'main' into extend-LuiguClientAPI-for-wc
JohannesDoberer Aug 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions client/luigi-element.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,4 +420,27 @@ export interface LuigiClient {
linkManager: () => LinkManager;
uxManager: () => UxManager;
publishEvent: (event: Event) => void;
/**
* Sets node parameters in Luigi Core. The parameters will be added to the URL.
* @param {Object} params
* @param {boolean} keepBrowserHistory
* @memberof LuigiClient
*/
addNodeParams: (params: Object, keepBrowserHistory: boolean) => void;
/**
* Returns the node parameters of the active URL.
* Node parameters are defined like URL query parameters but with a specific prefix allowing Luigi to pass them to the micro frontend view. The default prefix is **~** and you can use it in the following way: `https://my.luigi.app/home/products?~sort=asc&~page=3`.
* <!-- add-attribute:class:warning -->
* > **NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in node parameters are HTML-encoded.
* @param {boolean} shouldDesanitise defines whether the specially encoded characters should be desanitised
* @returns {Object} node parameters, where the object property name is the node parameter name without the prefix, and its value is the value of the node parameter. For example `{sort: 'asc', page: 3}`
* @memberof LuigiClient
*/
getNodeParams: (shouldDesanitise: boolean) => Object;
JohannesDoberer marked this conversation as resolved.
Show resolved Hide resolved
/**
* Sends anchor to Luigi Core. The anchor will be added to the URL.
* @param {string} anchor
* @memberof LuigiClient
*/
setAnchor: (anchor: string) => void;
JohannesDoberer marked this conversation as resolved.
Show resolved Hide resolved
}
8 changes: 8 additions & 0 deletions container/public/LuigiContainer.svelte.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export declare interface NodeParams {
[key: string]: string;
}
export default class LuigiContainer extends HTMLElement {
/**
* The URL of the microfrontend to be rendered
Expand Down Expand Up @@ -34,6 +37,11 @@ export default class LuigiContainer extends HTMLElement {
*/
active_feature_toggle_list: string[];

/**
* The parameters to be passed to the web-component-based micro frontend. Will not be passed to the compound children.
*/
node_params: NodeParams;

/**
* Updates the context of the microfrontend
* @param contextObj The context object to be updated
Expand Down
1 change: 1 addition & 0 deletions container/src/LuigiCompoundContainer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
export let locale;
export let theme;
export let active_feature_toggle_list;
export let node_params;

let compoundConfig;

Expand Down
1 change: 1 addition & 0 deletions container/src/LuigiContainer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
export let locale;
export let theme;
export let active_feature_toggle_list;
export let node_params;

let iframeHandle:
| {
Expand Down
22 changes: 22 additions & 0 deletions container/src/services/web-component-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,25 @@ export const registerEventListeners = (eventbusListeners, navNode, nodeId: strin
});
}
};

/**
* Desanitization of an object
* @param {Object} paramsMap
* @returns
*/
export const deSanitizeParamsMap = paramsMap => {
return Object.entries(paramsMap).reduce((sanitizedMap, paramPair) => {
sanitizedMap[deSanitizeParam(paramPair[0])] = deSanitizeParam(paramPair[1]);
return sanitizedMap;
}, {});
};

function deSanitizeParam(param: any) {
let desani = (String as any)(param)
.replaceAll('&lt;', '<')
.replaceAll('&gt;', '>')
.replaceAll('&quot;', '"')
.replaceAll('&#39;', "'")
.replaceAll('&sol;', '/');
return desani;
}
72 changes: 58 additions & 14 deletions container/src/services/webcomponents.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { DefaultCompoundRenderer, resolveRenderer, registerEventListeners } from './web-component-helpers';
import {
DefaultCompoundRenderer,
resolveRenderer,
registerEventListeners,
deSanitizeParamsMap
} from './web-component-helpers';
import { ContainerService } from './container.service';
import { Events } from '../constants/communication';

Expand All @@ -23,14 +28,22 @@ export class WebComponentService {
/** Creates a web component with tagname wc_id and adds it to wcItemContainer,
* if attached to wc_container
*/
attachWC(wc_id: string, wcItemPlaceholder: HTMLDivElement, wc_container, ctx, viewUrl: string, nodeId: string) {
attachWC(
wc_id: string,
wcItemPlaceholder: HTMLDivElement,
wc_container,
ctx,
viewUrl: string,
nodeId: string,
isSpecialMf?: boolean
) {
if (wc_container && wc_container.contains(wcItemPlaceholder)) {
const wc = document.createElement(wc_id);
if (nodeId) {
wc.setAttribute('nodeId', nodeId);
}

this.initWC(wc, wc_id, wc_container, viewUrl, ctx, nodeId);
this.initWC(wc, wc_id, wc_container, viewUrl, ctx, nodeId, isSpecialMf);
wc_container.replaceChild(wc, wcItemPlaceholder);
if (wc_container._luigi_node) {
wc_container._luigi_mfe_webcomponent = wc;
Expand Down Expand Up @@ -58,7 +71,7 @@ export class WebComponentService {
* @param wc_id a tagname that is used when creating the web component element
* @returns an object with the Luigi Client API
*/
createClientAPI(eventBusElement, nodeId: string, wc_id: string) {
createClientAPI(eventBusElement, nodeId: string, wc_id: string, isSpecialMf?: boolean) {
return {
linkManager: () => {
return {
Expand Down Expand Up @@ -101,12 +114,35 @@ export class WebComponentService {
},
luigiClientInit: () => {
this.dispatchLuigiEvent(Events.INITIALIZED, {});
},
addNodeParams: (params, keepBrowserHistory) => {
if (isSpecialMf) {
return;
}
this.dispatchLuigiEvent(Events.ADD_NODE_PARAMS_REQUEST, { params, keepBrowserHistory });
},
getNodeParams: shouldDesanitise => {
if (isSpecialMf) {
return {};
}
let result = this.thisComponent.getAttribute('node_params') || {};
result = JSON.parse(result);
if (shouldDesanitise) {
return deSanitizeParamsMap(result);
}
return result;
},
setAnchor: anchor => {
if (isSpecialMf) {
return;
}
this.dispatchLuigiEvent(Events.SET_ANCHOR_LINK_REQUEST, anchor);
}
};
}

initWC(wc: HTMLElement | any, wc_id, eventBusElement, viewUrl: string, ctx, nodeId: string) {
const clientAPI = this.createClientAPI(eventBusElement, nodeId, wc_id);
initWC(wc: HTMLElement | any, wc_id, eventBusElement, viewUrl: string, ctx, nodeId: string, isSpecialMf?: boolean) {
const clientAPI = this.createClientAPI(eventBusElement, nodeId, wc_id, isSpecialMf);

if (wc.__postProcess) {
const url =
Expand All @@ -126,8 +162,9 @@ export class WebComponentService {
*/
generateWCId(viewUrl: string) {
let charRep = '';
for (let i = 0; i < viewUrl.length; i++) {
charRep += viewUrl.charCodeAt(i).toString(16);
let normalizedViewUrl = new URL(viewUrl, location.href).href;
for (let i = 0; i < normalizedViewUrl.length; i++) {
charRep += normalizedViewUrl.charCodeAt(i).toString(16);
}
return 'luigi-wc-' + charRep;
}
Expand Down Expand Up @@ -240,7 +277,14 @@ export class WebComponentService {
/** Adds a web component defined by viewUrl to the wc_container and sets the node context.
* If the web component is not defined yet, it gets imported.
*/
renderWebComponent(viewUrl: string, wc_container: HTMLElement | any, context: any, node: any, nodeId?: any) {
renderWebComponent(
viewUrl: string,
wc_container: HTMLElement | any,
context: any,
node: any,
nodeId?: any,
isSpecialMf?: boolean
) {
const i18nViewUrl = this.processViewUrl(viewUrl, { context });
const wc_id =
node.webcomponent && node.webcomponent.tagName ? node.webcomponent.tagName : this.generateWCId(i18nViewUrl);
Expand All @@ -249,21 +293,21 @@ export class WebComponentService {
wc_container._luigi_node = node;

if (window.customElements.get(wc_id)) {
this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId);
this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId, isSpecialMf);
} else {
/** Custom import function, if defined */
if ((window as any).luigiWCFn) {
(window as any).luigiWCFn(i18nViewUrl, wc_id, wcItemPlaceholder, () => {
this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId);
this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId, isSpecialMf);
});
} else if (node.webcomponent && node.webcomponent.selfRegistered) {
this.includeSelfRegisteredWCFromUrl(node, i18nViewUrl, () => {
this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId);
this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId, isSpecialMf);
});
} else {
this.registerWCFromUrl(i18nViewUrl, wc_id)
.then(() => {
this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId);
this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId, isSpecialMf);
})
.catch(error => {
console.warn('ERROR =>', error);
Expand Down Expand Up @@ -367,7 +411,7 @@ export class WebComponentService {
renderer.attachCompoundItem(compoundCnt, compoundItemCnt);

const nodeId = wc.id || 'gen_' + index;
this.renderWebComponent(wc.viewUrl, compoundItemCnt, ctx, wc, nodeId);
this.renderWebComponent(wc.viewUrl, compoundItemCnt, ctx, wc, nodeId, true);
registerEventListeners(ebListeners, wc, nodeId);
});
wc_container.appendChild(compoundCnt);
Expand Down
39 changes: 37 additions & 2 deletions container/test-app/helloWorldWC.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ export default class extends HTMLElement {
template.innerHTML = `<section><p>Hello World!</p></section>`;

const templateBtn = document.createElement('template');
templateBtn.innerHTML = '<button>Click me!</button>';
templateBtn.innerHTML = '<button id="aButton">Click me!</button>';

const addNodeParamsBtn = document.createElement('template');
addNodeParamsBtn.innerHTML = '<button id="addNodeParams">add node params</button>';

const getNodeParamsBtn = document.createElement('template');
getNodeParamsBtn.innerHTML = '<button id="getNodeParams">get node params</button>';

const setAnchorBtn = document.createElement('template');
setAnchorBtn.innerHTML = '<button id="setAnchor">setAnchor</button>';

const empty = document.createElement('template');
empty.innerHTML = `<section><p>Test!</p><br/><br/></section>`;
Expand All @@ -19,13 +28,16 @@ export default class extends HTMLElement {
});
this._shadowRoot.appendChild(template.content.cloneNode(true));
this._shadowRoot.appendChild(templateBtn.content.cloneNode(true));
this._shadowRoot.appendChild(addNodeParamsBtn.content.cloneNode(true));
this._shadowRoot.appendChild(getNodeParamsBtn.content.cloneNode(true));
this._shadowRoot.appendChild(setAnchorBtn.content.cloneNode(true));

for (let index = 0; index < 10; index++) {
this._shadowRoot.appendChild(empty.content.cloneNode(true));
}

this.$paragraph = this._shadowRoot.querySelector('p');
this.$button = this._shadowRoot.querySelector('button');
this.$button = this._shadowRoot.querySelector('#aButton');
this.$button.addEventListener('click', () => {
if (this.LuigiClient) {
this.LuigiClient.uxManager().showAlert({
Expand All @@ -44,6 +56,29 @@ export default class extends HTMLElement {
});
}
});

this.$button2 = this._shadowRoot.querySelector('#addNodeParams');
this.$button2.addEventListener('click', () => {
if (this.LuigiClient) {
this.LuigiClient.addNodeParams({ Luigi: 'rocks' }, true);
}
});
this.$button3 = this._shadowRoot.querySelector('#getNodeParams');
this.$button3.addEventListener('click', () => {
if (this.LuigiClient) {
let nodeParams = this.LuigiClient.getNodeParams(false);
this.LuigiClient.uxManager().showAlert({
text: 'LuigiClient.getNodeParams()=' + JSON.stringify(nodeParams),
type: 'info'
});
}
});
this.$setAnchorBtn = this._shadowRoot.querySelector('#setAnchor');
this.$setAnchorBtn.addEventListener('click', () => {
if (this.LuigiClient) {
this.LuigiClient.setAnchor('#myAnchor');
}
});
}

set context(ctx) {
Expand Down
18 changes: 14 additions & 4 deletions container/test-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@
<luigi-compound-container
id="dashboard"
context='{"label": "Dashboard"}'
node_params='{"Luigi":"rocks"}'
></luigi-compound-container>
</div>
<div class="lui-wrapper" route="dashboard2">
Expand All @@ -306,6 +307,7 @@
context='{"label": "Dashboard2", "title":"Dashboard2"}'
skipInitCheck="false"
viewUrl="http://localhost:8080/nested.js"
node_params='{"Das ist ein":"compound tets"}'
></luigi-compound-container>
</div>
<!-- viewUrl="http://localhost:8080/nested.js" -->
Expand Down Expand Up @@ -334,7 +336,7 @@
</div>
<div class="lui-wrapper" route="test">
<luigi-container
viewURL="http://localhost:8080/test.js"
viewURL="/test.js"
webcomponent="true"
id="test-defer-init-false"
skipInitCheck="true"
Expand All @@ -356,16 +358,17 @@
locale="en"
theme="sap_fiori_3"
active_feature_toggle_list="['ft1','ft2']"
node_params='{"Luigi":"rocks from attri &<strong>asdf<strong> bute"}'
></luigi-container>
</div>
<div class="lui-wrapper" route="error-test">
<luigi-container
viewURL="https://luigiwebcomponents.gitlab.io/luigiwebcomponentmf/mainasdad.js"
webcomponent="true"
context='{"label": "Error Handler"}'
context=' {"label": "Error Handler" }'
></luigi-container>
<luigi-container
viewURL="test.js"
viewURL="/test.js"
webcomponent="true"
context='{"label": "Error Handler WC 2"}'
>
Expand Down Expand Up @@ -423,7 +426,7 @@
},
{
id: 'input1',
viewUrl: utilMFEDomain + '/input.js',
viewUrl: '/helloWorldWC.js',
context: {
title: 'Some input',
instant: true
Expand Down Expand Up @@ -539,6 +542,13 @@
console.log(event.detail);
}
);
luigiContainer.addEventListener(MFEventID.ADD_NODE_PARAMS_REQUEST, event => {
const params = event.detail.params;
console.log('params', params);
});
luigiContainer.addEventListener(MFEventID.SET_ANCHOR_LINK_REQUEST, event => {
console.log('anhor', event.detail);
});

//...
});
Expand Down
14 changes: 12 additions & 2 deletions container/test-app/nested.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import { LuigiElement } from './luigi-element.js';
export default class extends LuigiElement {
constructor() {
super({ openShadow: 'open', deferLuigiClientWCInit: true });
super({ openShadow: 'open', deferLuigiClientWCInit: false });

const template = document.createElement('template');

//remove buttons after review/AC
template.innerHTML = `
<section style="display: flex; height: 100%; flex-direction: column">
<button id="addNodeParams">addNodeParams</button>
<button id="getNodeParams">getNodeParams</button>
<header><slot name="header">header</slot></header>
<main style="flex: auto; overflow: auto"><slot name="content">content</slot><slot></slot></main>
<footer><slot name="footer">footer</slot></footer>
</section>
`;
this._shadowRoot.appendChild(template.content.cloneNode(true));
this.$button = this._shadowRoot.querySelector('#addNodeParams');
this.$button.addEventListener('click', () => {
this.LuigiClient.addNodeParams({ Luigi: 'rocks' }, true);
});
this.$button2 = this._shadowRoot.querySelector('#getNodeParams');
this.$button2.addEventListener('click', () => {
console.log('getNodeParams', this.LuigiClient.getNodeParams());
});
}

connectedCallback() {
Expand Down
Loading