Skip to content

Commit

Permalink
JNG-6155 sticky search params (#515)
Browse files Browse the repository at this point in the history
  • Loading branch information
noherczeg authored Feb 7, 2025
1 parent 5ea44fd commit 9fcdcab
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 7 deletions.
62 changes: 62 additions & 0 deletions docs/pages/01_ui_react.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,68 @@ const CustomDashboard: FC = () => {
};
----


=== Sticky Search params

Given we would like to add special search params to navigation and/or when the application is loaded, we can register a
hook to achieve this.

The `NavigationInterceptorHook` has two methods:

- decorateNavigation: `(to: string | Partial<Path>) => string`
- onBeforeInitialLoad?: `() => void`

The `decorateNavigation` function will be used as a decorator to modify every navigation call

The `onBeforeInitialLoad` function is call on the initial page load.

The hook also has a function property `stringifyPath` which can be used to convert a `Partial<Path>` to a `string` if
needed.

*src/custom/application-customizer.tsx:*
[source,typescriptjsx]
----
import type { BundleContext } from '@pandino/pandino-api';
import { useLocation, useNavigate, type Path } from 'react-router-dom';
import type { ApplicationCustomizer } from './interfaces';
import { NAVIGATION_INTERCEPTOR_INTERFACE_KEY, type NavigationInterceptorHook } from '~/components/CustomBreadcrumb';
export class DefaultApplicationCustomizer implements ApplicationCustomizer {
async customize(context: BundleContext): Promise<void> {
// register your implementations here
context.registerService<NavigationInterceptorHook>(NAVIGATION_INTERCEPTOR_INTERFACE_KEY, navigationInterceptor);
}
}
const navigationInterceptor: NavigationInterceptorHook = ({ stringifyPath }) => {
const SEACRH_PARAM = 'tenant';
const location = useLocation();
const navigate = useNavigate();
return {
decorateNavigation: (to: string | Partial<Path>) => {
const token = window.sessionStorage.getItem(SEACRH_PARAM)!;
const url = new URL(typeof to === 'string' ? to : stringifyPath(to), window.location.origin);
const searchParams = new URLSearchParams(url.search);
searchParams.set(SEACRH_PARAM, token);
return `${url.pathname}?${searchParams.toString()}`;
},
onBeforeInitialLoad: () => {
const token = window.sessionStorage.getItem(SEACRH_PARAM) || 'abc';
const searchParams = new URLSearchParams(window.location.search);
if (!searchParams.has(SEACRH_PARAM)) {
window.sessionStorage.setItem(SEACRH_PARAM, token);
searchParams.set(SEACRH_PARAM, token);
navigate(`${location.pathname}?${searchParams.toString()}${location.hash}`, {
replace: true,
});
}
},
};
};
----

=== Adding custom routes

Adding custom routes can be done via adding the `src/extra-routes.tsx` to the generator-ignore files and filling the
Expand Down
15 changes: 14 additions & 1 deletion judo-ui-react/src/main/resources/actor/src/App.tsx.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import LoadingButton from '@mui/lab/LoadingButton';
import { SnackbarProvider, closeSnackbar } from 'notistack';
import { BreadcrumbProvider, MdiIcon } from './components';
import { useTrackService } from '@pandino/react-hooks';
import { OBJECTCLASS } from '@pandino/pandino-api';
import { BreadcrumbProvider, MdiIcon, NAVIGATION_INTERCEPTOR_INTERFACE_KEY, type NavigationInterceptorHook, stringifyPath } from './components';
import { DialogProvider } from './components/dialog';
import { Layout } from './layout';
import { ScrollToTop } from './layout/ScrollToTop';
Expand All @@ -19,6 +21,17 @@ import { PrincipalProvider } from './auth';

function App() {
const location = useLocation();
const { service: navigationInterceptor } = useTrackService<NavigationInterceptorHook>(
`(${OBJECTCLASS}=${NAVIGATION_INTERCEPTOR_INTERFACE_KEY})`,
);
const customInit = navigationInterceptor
? navigationInterceptor({ stringifyPath }).onBeforeInitialLoad : () => {};

useEffect(() => {
if (customInit) {
customInit();
}
}, []);

return (
{{# if application.backgroundImage }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,33 @@ import Link from '@mui/material/Link';
import { useState, useContext, createContext, useMemo, useEffect, useCallback } from 'react';
import type { ReactNode } from 'react';
import { useLocation, useNavigate, Link as RouterLink } from 'react-router-dom';
import type { To } from 'react-router-dom';
import type { Path, To } from 'react-router-dom';
import { OBJECTCLASS } from '@pandino/pandino-api';
import { useTrackService } from '@pandino/react-hooks';
import { useTranslation } from 'react-i18next';
import { routeToDashboard } from '~/routes';
import { StackableDialogProvider } from './dialog/StackableDialogProvider';

export function stringifyPath(path: Partial<Path>): string {
const { pathname = '/', search = '', hash = '' } = path;
return `${pathname}${search}${hash}`;
}

interface BreadcrumbProviderProps {
children: ReactNode;
}

export type JudoNavigationSetTitle = (pageTitle: string) => void;

export const NAVIGATION_INTERCEPTOR_INTERFACE_KEY = 'JudoNavigationInterceptor';

export type NavigationInterceptorHook = ({ stringifyPath }: { stringifyPath: (path: Partial<Path>) => string }) => NavigationInterceptor;

export interface NavigationInterceptor {
decorateNavigation: (to: string | Partial<Path>) => string | Partial<Path>;
onBeforeInitialLoad?: () => void;
}

interface JudoNavigationProviderContext {
externalNavigate: (to: string) => void;
clearNavigate: (to: To) => void;
Expand Down Expand Up @@ -49,6 +65,8 @@ export const useJudoNavigation = () => {
export const BreadcrumbProvider = ({ children }: BreadcrumbProviderProps) => {
const navigate = useNavigate();
const location = useLocation();
const { service: navigationInterceptor } = useTrackService<NavigationInterceptorHook>(`(${OBJECTCLASS}=${NAVIGATION_INTERCEPTOR_INTERFACE_KEY})`);
const decorator = navigationInterceptor ? navigationInterceptor({ stringifyPath }).decorateNavigation : (to: string | Partial<Path>) => to;

const [breadcrumbItems, setBreadcrumbItems] = useState<BreadcrumbItem[]>([]);
const [nextBreadcrumbItem, setNextBreadcrumbItem] = useState<BreadcrumbItem>({
Expand Down Expand Up @@ -83,7 +101,7 @@ export const BreadcrumbProvider = ({ children }: BreadcrumbProviderProps) => {
});

if (triggerNavigation) {
navigate(lastItem.path);
navigate(decorator(lastItem.path));
}
} else {
setBreadcrumbItems([]);
Expand All @@ -93,7 +111,7 @@ export const BreadcrumbProvider = ({ children }: BreadcrumbProviderProps) => {
});

if (triggerNavigation) {
navigate(routeToDashboard());
navigate(decorator(routeToDashboard()));
}
}
}, [breadcrumbItems, nextBreadcrumbItem]);
Expand All @@ -109,7 +127,7 @@ export const BreadcrumbProvider = ({ children }: BreadcrumbProviderProps) => {
path: to,
});

navigate(to);
navigate(decorator(to));
},
navigate: (to: To) => {
if (nextBreadcrumbItem.label === null) {
Expand All @@ -122,7 +140,7 @@ export const BreadcrumbProvider = ({ children }: BreadcrumbProviderProps) => {
path: to,
});

navigate(to);
navigate(decorator(to));
},
back: () => {
breadCrumbBack(true);
Expand All @@ -149,11 +167,13 @@ export const BreadcrumbProvider = ({ children }: BreadcrumbProviderProps) => {
export const CustomBreadcrumb = () => {
const breadcrumbItems = useContext(BreadcrumbContextState);
const { t } = useTranslation();
const { service: navigationInterceptor } = useTrackService<NavigationInterceptorHook>(`(${OBJECTCLASS}=${NAVIGATION_INTERCEPTOR_INTERFACE_KEY})`);
const decorator = navigationInterceptor ? navigationInterceptor({ stringifyPath }).decorateNavigation : (to: string | Partial<Path>) => to;

return (
<Breadcrumbs id="application-breadcrumb" maxItems={5} separator="/">
{{# if ( hasDashboard application ) }}
<Link component={RouterLink} to={routeToDashboard()} sx={ { textDecoration: 'none' } }>
<Link component={RouterLink} to={decorator(routeToDashboard())} sx={ { textDecoration: 'none' } }>
<Typography>{ t('judo.breadcrumb.home', { defaultValue: 'Home' }) }</Typography>
</Link>
{{ else }}
Expand Down

0 comments on commit 9fcdcab

Please sign in to comment.