Skip to content

Commit

Permalink
AddNodeParams, getNodeParams and setAnchor for wc luigiClient (#3389)
Browse files Browse the repository at this point in the history
  • Loading branch information
JohannesDoberer authored Aug 18, 2023
1 parent afad02f commit 01114db
Show file tree
Hide file tree
Showing 18 changed files with 338 additions and 56 deletions.
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;
/**
* Sends anchor to Luigi Core. The anchor will be added to the URL.
* @param {string} anchor
* @memberof LuigiClient
*/
setAnchor: (anchor: string) => void;
}
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

0 comments on commit 01114db

Please sign in to comment.