diff --git a/docs/animation_during_reroute.md b/docs/animation_during_reroute.md new file mode 100644 index 00000000..723bc241 --- /dev/null +++ b/docs/animation_during_reroute.md @@ -0,0 +1,69 @@ +# Animation during reroute + +## Demo +![Animation during reroute](./assets/demoSpinner.gif) + + +## Implementation details +- Spinner appears only if transition from one route to another took more then 200ms. +- During transition ILC remove original MSs immediately and place fake cloned nodes to DOM, which in relust don't have +any JS mouse listeners. so we strongly recommend to use backdrop for spinner. +In any case, additionally it will cover any your bugs regarding interaction users with site during rerouting. + +## How to +You can use your own spinner by setting `tmplSpinner` property of `ilcConfig` in your main html template. +Example: +```html + + + + +``` \ No newline at end of file diff --git a/docs/assets/demoSpinner.gif b/docs/assets/demoSpinner.gif new file mode 100644 index 00000000..26f9fcd9 Binary files /dev/null and b/docs/assets/demoSpinner.gif differ diff --git a/docs/ilc_app_interface.md b/docs/ilc_app_interface.md index 9f5bea2a..0d30b00d 100644 --- a/docs/ilc_app_interface.md +++ b/docs/ilc_app_interface.md @@ -59,4 +59,5 @@ See more information about the [lifecycle functions here](https://single-spa.js. * `domElementGetter(): HTMLElement` - returns ref to `HTMLElement` that should be used as container to render app's content * `getCurrentPathProps(): {}` - returns _Props_ that were defined for current path * `getCurrentBasePath(): string` - returns same value as `basePath` param in `routerProps` query parameter -* `errorHandler(error, errorInfo = {}): void` - app MUST use it to propagate all unhandled errors \ No newline at end of file +* `errorHandler(error, errorInfo = {}): void` - app MUST use it to propagate all unhandled errors + diff --git a/ilc/client.js b/ilc/client.js index 406a6c8c..25c51081 100644 --- a/ilc/client.js +++ b/ilc/client.js @@ -3,7 +3,7 @@ import * as singleSpa from 'single-spa'; import Router from './common/router/ClientRouter'; import setupErrorHandlers from './client/errorHandler/setupErrorHandlers'; import {fragmentErrorHandlerFactory, crashIlc} from './client/errorHandler/fragmentErrorHandlerFactory'; -import { renderFakeSlot, addContentListener } from './client/pageTransitions'; +import handlePageTransaction from './client/handlePageTransaction'; import initSpaConfig from './client/initSpaConfig'; import setupPerformanceMonitoring from './client/performance'; import selectSlotsToRegister from './client/selectSlotsToRegister'; @@ -75,9 +75,9 @@ function isActiveFactory(appName, slotName) { let isActive = checkActivity(router.getCurrentRoute()); const wasActive = checkActivity(router.getPrevRoute()); - const willBeRendered = !wasActive && isActive; - const willBeRemoved = wasActive && !isActive; - let willBeRerendered = false; + let willBe; + !wasActive && isActive && (willBe = 'rendered'); + wasActive && !isActive && (willBe = 'removed'); if (isActive && wasActive && reload === false) { const oldProps = router.getPrevRouteProps(appName, slotName); @@ -95,17 +95,12 @@ function isActiveFactory(appName, slotName) { }); isActive = false; - willBeRerendered = true; + willBe = 'rerendered'; } } - if (willBeRendered) { - addContentListener(slotName); - } else if (willBeRemoved) { - renderFakeSlot(slotName); - } else if (willBeRerendered) { - renderFakeSlot(slotName); - addContentListener(slotName); + if (window.ilcConfig && window.ilcConfig.tmplSpinner) { + willBe && handlePageTransaction(slotName, willBe); } reload = false; diff --git a/ilc/client/pageTransitions.js b/ilc/client/handlePageTransaction.js similarity index 82% rename from ilc/client/pageTransitions.js rename to ilc/client/handlePageTransaction.js index 358bffc5..c4bfd393 100644 --- a/ilc/client/pageTransitions.js +++ b/ilc/client/handlePageTransaction.js @@ -44,7 +44,7 @@ const onAllSlotsLoaded = () => { window.dispatchEvent(new CustomEvent('ilc:all-slots-loaded')); }; -export const addContentListener = slotName => { +const addContentListener = slotName => { runGlobalSpinner(); if (window.location.hash) { @@ -67,7 +67,7 @@ export const addContentListener = slotName => { observer.observe(targetNode, { childList: true }); }; -export const renderFakeSlot = slotName => { +const renderFakeSlot = slotName => { const targetNode = getSlotElement(slotName); const clonedNode = targetNode.cloneNode(true); clonedNode.removeAttribute('id'); @@ -77,3 +77,20 @@ export const renderFakeSlot = slotName => { targetNode.style.display = 'none'; // we hide old slot because fake already in the DOM. hiddenSlots.push(targetNode); }; + +/** + * @param {string} slotName + * @param {string} willBe - possible values: rendered, removed, rerendered + */ +export default function (slotName, willBe) { + if (!slotName || !willBe) return; + + if (willBe === 'rendered') { + addContentListener(slotName); + } else if (willBe === 'removed') { + renderFakeSlot(slotName); + } else if (willBe === 'rerendered') { + renderFakeSlot(slotName); + addContentListener(slotName); + } +}