Skip to content

Commit

Permalink
Url params handling improvements - addCoreSearchParams & addNodeParams
Browse files Browse the repository at this point in the history
  • Loading branch information
stanleychh authored Dec 16, 2021
1 parent 4d91af5 commit 9765e2b
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 53 deletions.
19 changes: 15 additions & 4 deletions client/luigi-client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,10 +612,19 @@ export type getEventData = () => Context;
*/
export function getContext(): Context;
export type getContext = () => Context;

/**
* Sets node parameters in Luigi Core. The parameters will be added to the URL.
* @param {Object} params
* @memberof Lifecycle
* @example
* LuigiClient.addNodeParams({luigi:'rocks'});
* LuigiClient.addNodeParams({luigi:'rocks', false});
*/
export function addNodeParams(params: NodeParams, keepBrowserHistory: Boolean): void;
export type addNodeParams = (params: NodeParams, 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`.
* 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.
* @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}`
Expand Down Expand Up @@ -656,12 +665,14 @@ export type getCoreSearchParams = () => CoreSearchParams;
/**
* Sends search query parameters to Luigi core. If it is allowed on node level it will be added to url.
* @param {Object} searchParams
* @param {boolean} keepBrowserHistory
* @memberof Lifecycle
* @example
* LuigiClient.addCoreSearchParams({luigi:'rocks'});
* LuigiClient.addCoreSearchParams({luigi:'rocks', false});
*/
export function addCoreSearchParams(searchParam: CoreSearchParams): void;
export type addCoreSearchParams = (searchParam: CoreSearchParams) => void;
export function addCoreSearchParams(searchParams: CoreSearchParams, keepBrowserHistory: Boolean): void;
export type addCoreSearchParams = (searchParams: CoreSearchParams, keepBrowserHistory: Boolean) => void;

/**
* Returns the current client permissions as specified in the navigation node or an empty object. For details, see [Node parameters](navigation-parameters-reference.md).
Expand Down
33 changes: 28 additions & 5 deletions client/src/lifecycleManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,8 @@ class LifecycleManager extends LuigiClientBase {
return this.currentContext.context;
}

/**Returns a list of active feature toggles
/**
* Returns a list of active feature toggles
* @returns {Array} a list of feature toggle names
* @memberof Lifecycle
* @since 1.4.0
Expand All @@ -399,9 +400,28 @@ class LifecycleManager extends LuigiClientBase {
getActiveFeatureToggles() {
return this.currentContext.internal.activeFeatureToggleList;
}

/**
* Sets node parameters in Luigi Core. The parameters will be added to the URL.
* @param {Object} params
* @param {boolean} keepBrowserHistory
* @memberof Lifecycle
* @example
* LuigiClient.addNodeParams({luigi:'rocks'}, true);
*/
addNodeParams(params, keepBrowserHistory = true) {
if (params) {
helpers.sendPostMessageToLuigiCore({
msg: 'luigi.addNodeParams',
data: params,
keepBrowserHistory
});
}
}

/**
* 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`.
* 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.
* @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}`
Expand All @@ -412,6 +432,7 @@ class LifecycleManager extends LuigiClientBase {
getNodeParams() {
return this.currentContext.nodeParams;
}

/**
* Returns the dynamic path parameters of the active URL.
* Path parameters are defined by navigation nodes with a dynamic **pathSegment** value starting with **:**, such as **productId**.
Expand Down Expand Up @@ -441,15 +462,17 @@ class LifecycleManager extends LuigiClientBase {
/**
* Sends search query parameters to Luigi Core. If they are allowed on node level, the search parameters will be added to the URL.
* @param {Object} searchParams
* @param {boolean} keepBrowserHistory
* @memberof Lifecycle
* @example
* LuigiClient.addCoreSearchParams({luigi:'rocks'});
* LuigiClient.addCoreSearchParams({luigi:'rocks'}, false);
*/
addCoreSearchParams(searchParams) {
addCoreSearchParams(searchParams, keepBrowserHistory = true) {
if (searchParams) {
helpers.sendPostMessageToLuigiCore({
msg: 'luigi.addSearchParams',
data: searchParams
data: searchParams,
keepBrowserHistory
});
}
}
Expand Down
7 changes: 5 additions & 2 deletions client/src/luigi-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class LuigiClient {
getContext() {
return lifecycleManager.getContext();
}
addNodeParams(params, keepBrowserHistory) {
return lifecycleManager.addNodeParams(params, keepBrowserHistory);
}
getNodeParams() {
return lifecycleManager.getNodeParams();
}
Expand All @@ -59,8 +62,8 @@ class LuigiClient {
getCoreSearchParams() {
return lifecycleManager.getCoreSearchParams();
}
addCoreSearchParams(searchParams) {
return lifecycleManager.addCoreSearchParams(searchParams);
addCoreSearchParams(searchParams, keepBrowserHistory) {
return lifecycleManager.addCoreSearchParams(searchParams, keepBrowserHistory);
}
getClientPermissions() {
return lifecycleManager.getClientPermissions();
Expand Down
14 changes: 13 additions & 1 deletion core/src/App.html
Original file line number Diff line number Diff line change
Expand Up @@ -1587,9 +1587,21 @@
iframe.luigi.currentNode.clientPermissions &&
iframe.luigi.currentNode.clientPermissions.urlParameters
) {
RoutingHelpers.addSearchParamsFromClient(iframe.luigi.currentNode, e.data.data);
const { data, keepBrowserHistory } = e.data;
RoutingHelpers.addSearchParamsFromClient(
iframe.luigi.currentNode,
data,
keepBrowserHistory
);
}
}

if ('luigi.addNodeParams' === e.data.msg) {
if (isSpecialIframe) return;

const { data, keepBrowserHistory } = e.data;
LuigiRouting.addNodeParams(data, keepBrowserHistory);
}
});

// listeners are not automatically removed — cancel
Expand Down
44 changes: 35 additions & 9 deletions core/src/core-api/routing.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LuigiConfig } from '.';
import { GenericHelpers } from '../utilities/helpers';
import { GenericHelpers, RoutingHelpers } from '../utilities/helpers';
/**
* @name Routing
*/
Expand Down Expand Up @@ -40,18 +40,19 @@ class LuigiRouting {
* @memberof Routing
* @since 1.16.1
* @param {Object} params
* @param {boolean} keepBrowserHistory
* @example
* Luigi.routing().addSearchParams({luigi:'rocks', mario:undefined});
* Luigi.routing().addSearchParams({luigi:'rocks', mario:undefined}, false);
*/
addSearchParams(params) {
addSearchParams(params, keepBrowserHistory) {
if (!GenericHelpers.isObject(params)) {
console.log('Params argument must be an object');
return;
}
const url = new URL(location);
if (LuigiConfig.getConfigValue('routing.useHashRouting')) {
let [hashValue, givenQueryParamsString] = url.hash.split('?');
let searchParams = new URLSearchParams(givenQueryParamsString);
const [hashValue, givenQueryParamsString] = url.hash.split('?');
const searchParams = new URLSearchParams(givenQueryParamsString);
this._modifySearchParam(params, searchParams);
url.hash = hashValue;
if (searchParams.toString() !== '') {
Expand All @@ -60,19 +61,44 @@ class LuigiRouting {
} else {
this._modifySearchParam(params, url.searchParams);
}
window.history.pushState({}, '', url.href);

this.handleBrowserHistory(keepBrowserHistory, url.href);
LuigiConfig.configChanged();
}

//Adds and remove properties from searchParams
_modifySearchParam(params, searchParams) {
// Adds and remove properties from searchParams
_modifySearchParam(params, searchParams, paramPrefix) {
for (const [key, value] of Object.entries(params)) {
searchParams.set(key, value);
const paramKey = paramPrefix ? `${paramPrefix}${key}` : key;

searchParams.set(paramKey, value);
if (value === undefined) {
searchParams.delete(key);
}
}
}

addNodeParams(params, keepBrowserHistory) {
if (!GenericHelpers.isObject(params)) {
console.log('Params argument must be an object');
return;
}

const paramPrefix = RoutingHelpers.getContentViewParamPrefix();
const url = new URL(location);
this._modifySearchParam(params, url.searchParams, paramPrefix);

this.handleBrowserHistory(keepBrowserHistory, url.href);
LuigiConfig.configChanged();
}

handleBrowserHistory(keepBrowserHistory, href) {
if (keepBrowserHistory) {
window.history.pushState({}, '', href);
} else {
window.history.replaceState({}, '', href);
}
}
}

export const routing = new LuigiRouting();
33 changes: 18 additions & 15 deletions core/src/utilities/helpers/routing-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class RoutingHelpersClass {

getLastNodeObject(pathData) {
const lastElement = [...pathData.navigationPath].pop();
return lastElement ? lastElement : {};
return lastElement || {};
}

async getDefaultChildNode(pathData, childrenResolverFn) {
Expand Down Expand Up @@ -60,8 +60,8 @@ class RoutingHelpersClass {
}

encodeParams(dataObj) {
let queryArr = [];
for (let key in dataObj) {
const queryArr = [];
for (const key in dataObj) {
queryArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(dataObj[key]));
}
return queryArr.join('&');
Expand Down Expand Up @@ -130,14 +130,17 @@ class RoutingHelpersClass {
getQueryParam(paramName) {
return this.getQueryParams()[paramName];
}

getQueryParams() {
const hashRoutingActive = LuigiConfig.getConfigBooleanValue('routing.useHashRouting');
return hashRoutingActive ? this.getLocationHashQueryParams() : this.getLocationSearchQueryParams();
}

getLocationHashQueryParams() {
const queryParamIndex = location.hash.indexOf(this.defaultQueryParamSeparator);
return queryParamIndex !== -1 ? RoutingHelpers.parseParams(location.hash.slice(queryParamIndex + 1)) : {};
}

getLocationSearchQueryParams() {
return location.search ? RoutingHelpers.parseParams(location.search.slice(1)) : {};
}
Expand Down Expand Up @@ -170,7 +173,7 @@ class RoutingHelpersClass {
const hashRoutingActive = LuigiConfig.getConfigValue('routing.useHashRouting');

EventListenerHelpers.addEventListener('message', e => {
if ('refreshRoute' === e.data.msg && e.origin === window.origin) {
if (e.data.msg === 'refreshRoute' && e.origin === window.origin) {
const path = hashRoutingActive ? Routing.getHashPath() : Routing.getModifiedPathname();
callback(path);
}
Expand Down Expand Up @@ -198,7 +201,7 @@ class RoutingHelpersClass {
return pp + link;
}

let route = RoutingHelpers.buildRoute(node, `/${node.pathSegment}`);
const route = RoutingHelpers.buildRoute(node, `/${node.pathSegment}`);
return pp + GenericHelpers.replaceVars(route, pathParams, ':', false);
}

Expand Down Expand Up @@ -323,15 +326,15 @@ class RoutingHelpersClass {

setFeatureToggles(featureToggleProperty, path) {
let featureTogglesFromUrl;
let paramsMap = this.sanitizeParamsMap(this.parseParams(path.split('?')[1]));
const paramsMap = this.sanitizeParamsMap(this.parseParams(path.split('?')[1]));

if (paramsMap[featureToggleProperty]) {
featureTogglesFromUrl = paramsMap[featureToggleProperty];
}
if (!featureTogglesFromUrl) {
return;
}
let featureToggleList = featureTogglesFromUrl.split(',');
const featureToggleList = featureTogglesFromUrl.split(',');
if (featureToggleList.length > 0 && featureToggleList[0] !== '') {
featureToggleList.forEach(ft => LuigiFeatureToggles.setFeatureToggle(ft));
}
Expand Down Expand Up @@ -364,12 +367,12 @@ class RoutingHelpersClass {
const actionAndParams = elements[1].split('?');
// length 2 involves parameters, length 1 involves no parameters
if (actionAndParams.length === 2 || actionAndParams.length === 1) {
let action = actionAndParams[0];
const action = actionAndParams[0];
let params = actionAndParams[1];
// parse parameters, if any
if (params) {
params = params.split('&');
let paramObjects = [];
const paramObjects = [];
params.forEach(item => {
const param = item.split('=');
param.length === 2 && paramObjects.push({ [param[0]]: param[1] });
Expand Down Expand Up @@ -435,7 +438,7 @@ class RoutingHelpersClass {
realPath = this.resolveDynamicIntentPath(realPath, intentObject.params);
// get custom node param prefixes if any or default to ~
let nodeParamPrefix = LuigiConfig.getConfigValue('routing.nodeParamPrefix');
nodeParamPrefix = nodeParamPrefix ? nodeParamPrefix : '~';
nodeParamPrefix = nodeParamPrefix || '~';
realPath = realPath.concat(`?${nodeParamPrefix}`);
intentObject.params.forEach(param => {
realPath = realPath.concat(Object.keys(param)[0]); // append param name
Expand Down Expand Up @@ -489,7 +492,7 @@ class RoutingHelpersClass {
}

prepareSearchParamsForClient(currentNode) {
let filteredObj = {};
const filteredObj = {};
if (currentNode && currentNode.clientPermissions && currentNode.clientPermissions.urlParameters) {
Object.keys(currentNode.clientPermissions.urlParameters).forEach(key => {
if (key in LuigiRouting.getSearchParams() && currentNode.clientPermissions.urlParameters[key].read === true) {
Expand All @@ -500,9 +503,9 @@ class RoutingHelpersClass {
return filteredObj;
}

addSearchParamsFromClient(currentNode, searchParams) {
addSearchParamsFromClient(currentNode, searchParams, keepBrowserHistory) {
if (currentNode && currentNode.clientPermissions && currentNode.clientPermissions.urlParameters) {
let filteredObj = {};
const filteredObj = {};
Object.keys(currentNode.clientPermissions.urlParameters).forEach(key => {
if (key in searchParams && currentNode.clientPermissions.urlParameters[key].write === true) {
filteredObj[key] = searchParams[key];
Expand All @@ -511,7 +514,7 @@ class RoutingHelpersClass {
}
});
if (Object.keys(filteredObj).length > 0) {
LuigiRouting.addSearchParams(filteredObj);
LuigiRouting.addSearchParams(filteredObj, keepBrowserHistory);
}
}
}
Expand All @@ -533,7 +536,7 @@ class RoutingHelpersClass {
getPageNotFoundRedirectPath(notFoundPath, isAnyPathMatched = false) {
const pageNotFoundHandler = LuigiConfig.getConfigValue('routing.pageNotFoundHandler');
if (typeof pageNotFoundHandler === 'function') {
//custom 404 handler is provided, use it
// custom 404 handler is provided, use it
const result = pageNotFoundHandler(notFoundPath, isAnyPathMatched);
if (result && result.redirectTo) {
return result.redirectTo;
Expand Down
Loading

0 comments on commit 9765e2b

Please sign in to comment.