refactor: Cookie Notice refactorisation, removes useless cn-modal-wrapper

Template changes: removes .cn-modal-wrapper
This commit is contained in:
Jérémy Munsch 2024-06-13 11:16:12 +02:00
parent 8cc4dc42f0
commit 96f73ea7bf
No known key found for this signature in database
GPG key ID: 18361AF3BFEC0FEC
4 changed files with 187 additions and 229 deletions

View file

@ -37,10 +37,8 @@
</div>
{{-- 2ND PAGE CUSTOMIZE PANEL --}}
<div class="cn-modal-container d-none">
<div class="cn-modal-wrapper">
{{-- COOKIES LIST --}}
<ul id="cn-modal-list"></ul>
</div>
{{-- COOKIES LIST --}}
<ul id="cn-modal-list"></ul>
</div>
</div>
{{-- NAV BAR --}}

View file

@ -37,10 +37,8 @@
</div>
<!-- 2ND PAGE CUSTOMIZE PANEL -->
<div class="cn-modal-container d-none">
<div class="cn-modal-wrapper">
<!-- COOKIES LIST -->
<ul id="cn-modal-list"></ul>
</div>
<!-- COOKIES LIST -->
<ul id="cn-modal-list"></ul>
</div>
</div>
<!-- NAV BAR -->

View file

@ -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 {

View file

@ -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<string, CookieOptions>;
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<HTMLElement>;
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<HTMLElement> | null,
/** Cookie template for unordered cookie list */
template?: HTMLDivElement|null
} = {};
// * -- END DOM ELEMENTS
constructor(config: Record<string, CookieOptions>) {
// * 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<HTMLElement>,
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<K extends keyof typeof this.domElList,T extends Exclude<typeof this.domElList[K], null | undefined>>
(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<HTMLElement>;
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<typeof this.domElList[keyof typeof this.domElList], undefined>;
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 {