From 96f73ea7bfa72cc5ae4cb7fbd9cf7dcaf94b6f74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 13 Jun 2024 11:16:12 +0200 Subject: [PATCH] refactor: Cookie Notice refactorisation, removes useless cn-modal-wrapper Template changes: removes .cn-modal-wrapper --- examples/laravel/templates/standard.blade.php | 6 +- examples/wordpress/templates/standard.php | 6 +- src/scss/themes/standard.scss | 72 ++-- src/ts/index.ts | 332 ++++++++---------- 4 files changed, 187 insertions(+), 229 deletions(-) diff --git a/examples/laravel/templates/standard.blade.php b/examples/laravel/templates/standard.blade.php index 8408280..37853b8 100644 --- a/examples/laravel/templates/standard.blade.php +++ b/examples/laravel/templates/standard.blade.php @@ -37,10 +37,8 @@ {{-- 2ND PAGE CUSTOMIZE PANEL --}}
-
- {{-- COOKIES LIST --}} -
    -
    + {{-- COOKIES LIST --}} +
    {{-- NAV BAR --}} diff --git a/examples/wordpress/templates/standard.php b/examples/wordpress/templates/standard.php index f5f3240..bfea710 100644 --- a/examples/wordpress/templates/standard.php +++ b/examples/wordpress/templates/standard.php @@ -37,10 +37,8 @@
    -
    - -
      -
      + +
      diff --git a/src/scss/themes/standard.scss b/src/scss/themes/standard.scss index e223a3b..cf4d28d 100644 --- a/src/scss/themes/standard.scss +++ b/src/scss/themes/standard.scss @@ -372,49 +372,47 @@ $cn-w-modal-width: $cn-cookie-modal-width; // * 2nd Page MODAL .cn-modal-container { - .cn-modal-wrapper { - position: relative; + position: relative; + display: flex; + overflow: auto; + flex-direction: column; + align-items: center; + + ul { display: flex; - overflow: auto; flex-direction: column; + padding: 0; + margin: 0; + list-style: none; + } + + .cn-cookie-wrapper { + display: flex; + flex-direction: row; align-items: center; + justify-content: space-between; + padding: 20px; + border: 1px solid #{$ck-bgcolor-list}; + border-radius: 20px; + margin: 10px 0; + } - ul { - display: flex; - flex-direction: column; - padding: 0; - margin: 0; - list-style: none; - } + /* stylelint-disable-next-line no-descending-specificity */ + span { + color: $ck-color-list; + font-family: $ck-font-family-list; + font-size: $ck-font-size-list; + } - .cn-cookie-wrapper { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - padding: 20px; - border: 1px solid #{$ck-bgcolor-list}; - border-radius: 20px; - margin: 10px 0; - } + a { + font-family: $ck-font-family-list; + font-size: $ck-font-size-list; + } - /* stylelint-disable-next-line no-descending-specificity */ - span { - color: $ck-color-list; - font-family: $ck-font-family-list; - font-size: $ck-font-size-list; - } - - a { - font-family: $ck-font-family-list; - font-size: $ck-font-size-list; - } - - label { - color: $ck-color-list; - font-family: $ck-font-family-list; - font-size: $ck-font-size-list; - } + label { + color: $ck-color-list; + font-family: $ck-font-family-list; + font-size: $ck-font-size-list; } .cn-cookie { diff --git a/src/ts/index.ts b/src/ts/index.ts index 2bea15b..220b2ae 100644 --- a/src/ts/index.ts +++ b/src/ts/index.ts @@ -1,6 +1,7 @@ /** 5.0.4 */ export default class CookieNotice { + /** The CN cookie status */ browserCookieName = "cn-init"; /** Cookie life time */ @@ -18,26 +19,43 @@ export default class CookieNotice { /** Cookie expiration date as specified in the cookie itself */ cookieExpirationDate: Date; + /** Holding all cookie configs */ config: Record; - modalBg: HTMLDivElement; - cookieContainer: HTMLDivElement; - bannerContainer: HTMLDivElement; - modalContainer: HTMLDivElement; - cookieShowModalBtn: HTMLButtonElement; - cookieCloseModalBtn: HTMLButtonElement; + /** Prevent user from closing modal */ + restrictCloseModal: boolean = true; - bannerContent: HTMLDivElement; - acceptAllBtn: HTMLButtonElement; - denyAllBtn: HTMLButtonElement; - customizeBtn: HTMLButtonElement; - cookiesUnorderedList: HTMLUListElement; - cookieLinkShowCustomizeModalList: NodeListOf; - modalWrapper: HTMLDivElement; - template: HTMLDivElement; - - restrictCloseModal: boolean; + // * -- DOM ELEMENTS + domElList: { + /** Modal transparent background => div.cn-modal-bg */ + modalBg?: HTMLDivElement|null, + /** Modal window container for the cookie panel and button => div#cn-wrapper */ + cookieContainer?: HTMLDivElement|null|null, + /** Modal container (wrapping pages) => div#cn-banner */ + bannerContainer?: HTMLDivElement|null, + /** Modal banner (1rst page) container => div.cn-banner-content */ + bannerContent?: HTMLDivElement|null, + /** Modal cookie list (2nd page) container => div.cn-modal-container */ + modalContainer?: HTMLDivElement | null, + /** The big cookie button => button#cn-cookie-btn */ + cookieShowModalBtn?: HTMLButtonElement | null, + /** Modal container close button => button#cn-close-modal */ + cookieCloseModalBtn?: HTMLButtonElement | null, + /** Modal container accept all cookies button => button#cn-accept-banner */ + acceptAllBtn?: HTMLButtonElement|null, + /** Modal container deny all cookies button => button#cn-deny-banner */ + denyAllBtn?: HTMLButtonElement | null, + /** Modal container switch page / (validate and) close button => button#cn-settings-banner */ + customizeBtn?: HTMLButtonElement | null, + /** Modal unordered cookie list => ul#cn-modal-list */ + cookiesUnorderedList?: HTMLUListElement | null, + /** List of all links that will open cookie notice */ + cookieLinkShowCustomizeModalList?: NodeListOf | null, + /** Cookie template for unordered cookie list */ + template?: HTMLDivElement|null + } = {}; + // * -- END DOM ELEMENTS constructor(config: Record) { // * Setup Config @@ -51,137 +69,8 @@ export default class CookieNotice { } this.cookieExpirationDate = cookieExpirationDate; - // * Inject DOM modal BG - const modalBg = document.createElement("div"); - modalBg.classList.add("cn-modal-bg"); - document.body.append(modalBg); - this.modalBg = modalBg; - - // * Setup Dom Elements - const cookieContainer = document.querySelector( - "div#cn-wrapper" - ) as HTMLDivElement | null, - cookieShowModalBtn = document.querySelector( - "button#cn-cookie-btn" - ) as HTMLButtonElement | null, - cookieCloseModalBtn = document.querySelector( - "button#cn-close-modal" - ) as HTMLButtonElement | null, - cookieLinkShowCustomizeModalPageList = document.querySelectorAll( - ".cn-cookie-link" - ) as NodeListOf, - bannerContainer = document.querySelector( - "div#cn-banner" - ) as HTMLDivElement | null, - bannerContent = document.querySelector( - "div.cn-banner-content" - ) as HTMLDivElement | null, - modalContainer = document.querySelector( - "div.cn-modal-container" - ) as HTMLDivElement | null, - acceptAllBtn = document.querySelector( - "button#cn-accept-banner" - ) as HTMLButtonElement | null, - denyAllBtn = document.querySelector( - "button#cn-deny-banner" - ) as HTMLButtonElement | null, - customizeBtn = document.querySelector( - "button#cn-settings-banner" - ) as HTMLButtonElement | null, - cookiesUnorderedList = document.querySelector( - "ul#cn-modal-list" - ) as HTMLUListElement | null, - modalWrapper = document.querySelector( - "div.cn-modal-wrapper" - ) as HTMLDivElement, - template = document.querySelector( - "div#cn-li-template" - ) as HTMLDivElement | null; - if (!cookieContainer) { - throw new Error("Cannot find div#cn-wrapper"); - } - if (!cookieShowModalBtn) { - throw new Error("Cannot find 'button#cn-cookie-btn' element"); - } - if (!cookieCloseModalBtn) { - throw new Error("Cannot find 'button#cn-close-modal' element"); - } - if (!bannerContainer) { - throw new Error("Cannot find 'div#cn-banner' element"); - } - if (!bannerContent) { - throw new Error("Cannot find 'div.cn-banner-content' element"); - } - if (!modalContainer) { - throw new Error("Cannot find 'div.cn-modal-container' element"); - } - if (!acceptAllBtn) { - throw new Error("Cannot find 'button#cn-accept-banner' element"); - } - if (!denyAllBtn) { - throw new Error("Cannot find 'button#cn-deny-banner' element"); - } - if (!customizeBtn) { - throw new Error("Cannot find 'button.cn-settings-banner' element"); - } - if (!cookiesUnorderedList) { - throw new Error("Cannot find 'ul#cn-modal-list' element"); - } - if (!modalWrapper) { - throw new Error("Cannot find 'div.cn-modal-wrapper' element"); - } - if (!template) { - throw new Error("Cannot find 'div#cn-li-template' element"); - } - - this.cookieShowModalBtn = cookieShowModalBtn; - this.cookieCloseModalBtn = cookieCloseModalBtn; - this.cookieLinkShowCustomizeModalList = - cookieLinkShowCustomizeModalPageList; - this.cookieContainer = cookieContainer; - this.bannerContainer = bannerContainer; - this.bannerContent = bannerContent; - this.cookiesUnorderedList = cookiesUnorderedList; - this.modalWrapper = modalWrapper; - this.modalContainer = modalContainer; - this.acceptAllBtn = acceptAllBtn; - this.denyAllBtn = denyAllBtn; - this.customizeBtn = customizeBtn; - - this.template = template; - const memoryTemplate = this.template.cloneNode(true) as HTMLDivElement; - this.template.remove(); - this.template = memoryTemplate; - - this.restrictCloseModal = true; - - // * Setup Events - this.addEventPrevent(this.modalBg, "click", this.closeCookieModal); - this.addEventPrevent( - this.cookieCloseModalBtn, - "click", - this.closeCookieModal - ); - this.addEventPrevent(this.acceptAllBtn, "click", this.acceptAllCookies); - this.addEventPrevent(this.denyAllBtn, "click", this.denyAllCookies); - this.addEventPrevent( - this.customizeBtn, - "click", - this.switchToModalPageOrClose - ); - this.addEventPrevent( - this.cookieShowModalBtn, - "click", - this.toggleCookieModal - ); - this.cookieLinkShowCustomizeModalList.forEach((cookieLink) => { - this.addEventPrevent( - cookieLink, - "click", - this.openCookieModalToCookiePage - ); - this.addEventKbdValidate(cookieLink, this.openCookieModalToCookiePage); - }); + this.initDomElements(); + this.setupEvents(); // * Setup CN Cookie if (!this.cookiesMatchesConfig() || this.cookiesOlderThan(new Date())) { @@ -221,9 +110,11 @@ export default class CookieNotice { * Update restrict for closing cookie modal */ updateRestrictCloseModal() { + const cookieCloseModalBtn = this.getDomEl("cookieCloseModalBtn"), + modalBg = this.getDomEl("modalBg"); this.restrictCloseModal = (Object.values(this.getCookieStatusList()).includes(CookieStatus.wait)) ? true : false; - (this.restrictCloseModal) ? this.cookieCloseModalBtn.classList.add("d-none") : this.cookieCloseModalBtn.classList.remove("d-none"); - (this.restrictCloseModal) ? this.modalBg.classList.add("cn-modal-bg-active") : this.modalBg.classList.remove("cn-modal-bg-active"); + (this.restrictCloseModal) ? cookieCloseModalBtn.classList.add("d-none") : cookieCloseModalBtn.classList.remove("d-none"); + (this.restrictCloseModal) ? modalBg.classList.add("cn-modal-bg-active") : modalBg.classList.remove("cn-modal-bg-active"); } /** @@ -625,12 +516,12 @@ export default class CookieNotice { openCookieModalToCookiePage() { document.body.classList.add("cn-open"); this.resetCookieModal(); - this.bannerContent.classList.add("d-none"); - this.modalContainer.classList.remove("d-none"); - this.bannerContainer.classList.add("cn-modal-page"); + this.getDomEl("bannerContent").classList.add("d-none"); + this.getDomEl("modalContainer").classList.remove("d-none"); + this.getDomEl("bannerContainer").classList.add("cn-modal-page"); // Scroll to the top of the cookies list - this.modalWrapper.scrollTop = 0; + this.getDomEl("modalContainer").scrollTop = 0; this.setupCookieModalList(); this.setTabIndex(); } @@ -644,9 +535,9 @@ export default class CookieNotice { document.body.classList.remove("cn-open"); if (!this.isOnBannerPage()) { // * Make toggle page to banner on closing - this.bannerContent.classList.toggle("d-none"); - this.modalContainer.classList.toggle("d-none"); - this.bannerContainer.classList.remove("cn-modal-page"); + this.getDomEl("bannerContent").classList.toggle("d-none"); + this.getDomEl("modalContainer").classList.toggle("d-none"); + this.getDomEl("bannerContainer").classList.remove("cn-modal-page"); } this.unsetTabIndex(); this.setupCookieModalList(); @@ -664,20 +555,20 @@ export default class CookieNotice { setTimeout(() => { const focusableSelector = "a[href], button, [role=\"button\"], input, embed, object, iframe, textarea, select, details, [tabindex]:not([tabindex=\"-1\"])"; - + const cookieContainer = this.getDomEl("cookieContainer"); // * Only focusable elements [...document.body.querySelectorAll(focusableSelector)] .filter( (el) => !el.hasAttribute("disabled") && !el.getAttribute("aria-hidden") && - !this.cookieContainer.contains(el) + !cookieContainer.contains(el) ) .forEach((el) => { (el as HTMLElement).setAttribute("tabindex", "-1"); (el as HTMLElement).classList.add("cn-disabled"); }); - [...this.cookieContainer.querySelectorAll(focusableSelector)] + [...cookieContainer.querySelectorAll(focusableSelector)] .filter( (el) => !el.hasAttribute("disabled") && @@ -688,13 +579,13 @@ export default class CookieNotice { (el as HTMLElement).offsetHeight || el.getClientRects().length ) && - !this.cookieContainer.contains(el) + !cookieContainer.contains(el) ) .forEach((el) => { (el as HTMLElement).setAttribute("tabindex", "0"); (el as HTMLElement).classList.remove("cn-disabled"); }); - this.cookieShowModalBtn.setAttribute("tabindex", "-1"); + this.getDomEl("cookieShowModalBtn").setAttribute("tabindex", "-1"); }, 500); } @@ -708,7 +599,7 @@ export default class CookieNotice { (el as HTMLElement).setAttribute("tabindex", "0"); (el as HTMLElement).classList.remove("cn-disabled"); }); - this.cookieShowModalBtn.setAttribute("tabindex", "0"); + this.getDomEl("cookieShowModalBtn").setAttribute("tabindex", "0"); }, 500); } @@ -716,23 +607,24 @@ export default class CookieNotice { * Reset the modal view state */ resetCookieModal() { - this.bannerContent.classList.remove("d-none"); - this.modalContainer.classList.add("d-none"); - this.bannerContainer.classList.remove("cn-modal-page"); - if (!this.customizeBtn.dataset.openModal) { + const customizeBtn = this.getDomEl("customizeBtn"); + this.getDomEl("bannerContent").classList.remove("d-none"); + this.getDomEl("modalContainer").classList.add("d-none"); + this.getDomEl("bannerContainer").classList.remove("cn-modal-page"); + if (!customizeBtn.dataset.openModal) { throw new Error("Cannot find attribut data-open-modal"); } - this.customizeBtn.innerHTML = this.customizeBtn.dataset.openModal; + customizeBtn.innerHTML = customizeBtn.dataset.openModal; } /** * Set the cookies list in the modal */ setupCookieModalList() { - this.cookiesUnorderedList.innerHTML = ""; + this.getDomEl("cookiesUnorderedList").innerHTML = ""; Object.entries(this.config).forEach(([cookieName, cookieConfig]) => { const cookie = document.createElement("li"); - const childItem = this.template.children.item( + const childItem = this.getDomEl("template").children.item( 0 ) as HTMLDivElement | null; if (childItem === null) { @@ -784,7 +676,7 @@ export default class CookieNotice { this.addEvent(cnSwitchInput, "click", () => toggleCookie(cookieName)); this.addEventKbdValidate(cnSwitch, () => cnSwitchInput.click()); - this.cookiesUnorderedList.append(cookie); + this.getDomEl("cookiesUnorderedList").append(cookie); if (!this.hasBrowserCookie()) { // * Ldcweb cookie is not set. @@ -830,32 +722,34 @@ export default class CookieNotice { * Toggle Cookie modal pages */ toggleCookieModalPages() { - this.bannerContent.classList.toggle("d-none"); - this.modalContainer.classList.toggle("d-none"); - this.bannerContainer.classList.toggle("cn-modal-page"); + this.getDomEl("bannerContent").classList.toggle("d-none"); + this.getDomEl("modalContainer").classList.toggle("d-none"); + this.getDomEl("bannerContainer").classList.toggle("cn-modal-page"); if (!this.isOnBannerPage()) { - this.bannerContainer.classList.add("cn-modal-page"); + this.getDomEl("bannerContainer").classList.add("cn-modal-page"); this.denyAllWaitingCookies(); } this.setTabIndex(); this.setupCookieModalList(); - this.togglePersonalizeBtn(); + this.toggleCustomizeBtn(); } /** * Toggle customize btn */ - togglePersonalizeBtn() { - const customizeBtnBannerDataset = this.customizeBtn.dataset; - this.modalWrapper.scrollTop = 0; - if (this.bannerContent.classList.contains("d-none")) { - this.customizeBtn.innerHTML = + toggleCustomizeBtn() { + const customizeBtn = this.getDomEl("customizeBtn"), + customizeBtnBannerDataset = this.getDomEl("customizeBtn").dataset; + + this.getDomEl("modalContainer").scrollTop = 0; + if (this.getDomEl("bannerContent").classList.contains("d-none")) { + customizeBtn.innerHTML = customizeBtnBannerDataset.openModal as string; - this.customizeBtn.title = customizeBtnBannerDataset.openModal as string; + customizeBtn.title = customizeBtnBannerDataset.openModal as string; } else { - this.customizeBtn.innerHTML = + customizeBtn.innerHTML = customizeBtnBannerDataset.closeModal as string; - this.customizeBtn.title = customizeBtnBannerDataset.closeModal as string; + return customizeBtn.title = customizeBtnBannerDataset.closeModal as string; } } @@ -863,7 +757,7 @@ export default class CookieNotice { * Get the actual page: banner or modal (with cookie choices) */ isOnBannerPage(): boolean { - return this.modalContainer.classList.contains("d-none"); + return this.getDomEl("modalContainer").classList.contains("d-none"); } /** @@ -883,6 +777,76 @@ export default class CookieNotice { throw new Error(`Un handled cookie status ${status}`); } } + + /** Setup Events */ + private setupEvents(): void { + this.addEventPrevent(this.getDomEl("modalBg"), "click", this.closeCookieModal); + this.addEventPrevent(this.getDomEl("cookieCloseModalBtn"), "click", this.closeCookieModal ); + this.addEventPrevent(this.getDomEl("acceptAllBtn"), "click", this.acceptAllCookies); + this.addEventPrevent(this.getDomEl("denyAllBtn"), "click", this.denyAllCookies); + this.addEventPrevent(this.getDomEl("customizeBtn"), "click", this.switchToModalPageOrClose ); + this.addEventPrevent(this.getDomEl("cookieShowModalBtn"), "click", this.toggleCookieModal ); + this.getDomEl("cookieLinkShowCustomizeModalList").forEach((cookieLink) => { + this.addEventPrevent( + cookieLink, + "click", + this.openCookieModalToCookiePage + ); + this.addEventKbdValidate(cookieLink, this.openCookieModalToCookiePage); + }); + } + + /** Get dom element */ + private getDomEl> + (item: K): T { + // Exclude null|undefined as elements are verified in initDomElements + return this.domElList[item] as T; + } + + /** Init Dom elements selector */ + private initDomElements(): void { + // * Inject DOM modal BG + const modalBg = document.createElement("div"); + modalBg.classList.add("cn-modal-bg"); + document.body.append(modalBg); + this.domElList.modalBg = modalBg; + + // * Setup Dom Elements + this.domElList.cookieLinkShowCustomizeModalList = document.querySelectorAll( + ".cn-cookie-link" + ) as NodeListOf; + + const selectorList = { + "div#cn-wrapper": "cookieContainer", + "button#cn-cookie-btn": "cookieShowModalBtn", + "button#cn-close-modal": "cookieCloseModalBtn", + "div#cn-banner": "bannerContainer", + "div.cn-banner-content": "bannerContent", + "div.cn-modal-container": "modalContainer", + "button#cn-accept-banner": "acceptAllBtn", + "button#cn-deny-banner": "denyAllBtn", + "button#cn-settings-banner": "customizeBtn", + "ul#cn-modal-list": "cookiesUnorderedList", + "div#cn-li-template": "template", + }; + + Object.entries(selectorList).forEach(entry => { + const selector = entry[0], + domElItemKey = entry[1] as keyof typeof this.domElList; + let itemEntry = this.domElList[domElItemKey]; + const domEl = document.querySelector(selector) as Exclude; + itemEntry = domEl as typeof itemEntry; + if (!itemEntry) { + throw new Error(`Cannot find dom element ${selector}`); + } + this.domElList = {...this.domElList, [domElItemKey]: itemEntry}; + }); + + // * Clone and remove original template node + const memoryTemplate = this.domElList.template!.cloneNode(true) as HTMLDivElement; + this.domElList.template!.remove(); + this.domElList.template = memoryTemplate; + } } enum CookieStatus {