diff --git a/packages/sitecore-jss-angular/src/public_api.ts b/packages/sitecore-jss-angular/src/public_api.ts index 6d01d89ced..0fed4d5219 100644 --- a/packages/sitecore-jss-angular/src/public_api.ts +++ b/packages/sitecore-jss-angular/src/public_api.ts @@ -20,6 +20,7 @@ export { TextDirective } from './components/text.directive'; export { LayoutService } from './layout.service'; export { LayoutServiceError } from './layout-service-error'; export { JssModule } from './lib.module'; +export { handleEditorAnchors } from './utils'; export { dataApi, mediaApi, diff --git a/packages/sitecore-jss-angular/src/utils.ts b/packages/sitecore-jss-angular/src/utils.ts new file mode 100644 index 0000000000..d1a6e11831 --- /dev/null +++ b/packages/sitecore-jss-angular/src/utils.ts @@ -0,0 +1,40 @@ +import { isExperienceEditorActive } from '@sitecore-jss/sitecore-jss'; + +/** + * @description in Experience Editor with an Angular sample app, anchor tags + * with both onclick and href attributes will use the href, blocking the onclick from firing. + * This function makes it so the anchor tags function as intended in an Angular sample when using Experience Editor + * + * The Mutation Observer API is used to observe changes to the body, then select all elements with href="#" and an onclick, + * and replaces the # value with javascript:void(0); which prevents the anchor tag from blocking the onclick event handler. + * @see Mutation Observer API: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/MutationObserver + */ +export const handleEditorAnchors = () => { + // Angular gives the href attribute priority over the onclick attribute if both are present, so we must replace + // the href attribute to avoid overriding the onclick in Experience Editor + if (!window || !isExperienceEditorActive()) { + return; + } + const targetNode = document.querySelector('body'); + const callback = (mutationList: MutationRecord[]) => { + mutationList.forEach((mutation: MutationRecord) => { + const btns: NodeListOf = document.querySelectorAll( + '.scChromeDropDown > a[href="#"], a[onclick]' + ); + if (mutation.type === 'childList') { + btns.forEach((link: HTMLAnchorElement) => { + link.href = 'javascript:void(0);'; + }); + } + return; + }); + }; + const observer: MutationObserver = new MutationObserver(callback); + const observerOptions = { + childList: true, + subtree: true, + }; + if (targetNode) { + observer.observe(targetNode, observerOptions); + } +}; diff --git a/samples/angular/src/main.ts b/samples/angular/src/main.ts index 13dff5eb0a..35b26b2620 100644 --- a/samples/angular/src/main.ts +++ b/samples/angular/src/main.ts @@ -3,6 +3,7 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; +import { handleEditorAnchors } from '@sitecore-jss/sitecore-jss-angular'; if (environment.production) { enableProdMode(); @@ -14,4 +15,7 @@ document.addEventListener('DOMContentLoaded', () => { platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.log(err)); -}); + + // allows Experience Editor anchor tag's onclick events to properly propagate + handleEditorAnchors(); + });