From c0b326fa6b0c04da8c564bb3e30ab79e6e387d77 Mon Sep 17 00:00:00 2001 From: Matronator <5470780+matronator@users.noreply.github.com> Date: Wed, 26 Apr 2023 17:34:45 +0200 Subject: [PATCH] fix memory leak --- .npmignore | 6 +++++ package.json | 2 +- src/axette.ts | 70 ++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/.npmignore b/.npmignore index e2d6fa4..3e59256 100644 --- a/.npmignore +++ b/.npmignore @@ -13,3 +13,9 @@ npm-debug.log* dts-readme.md src/index-old.ts .github +index.html +CHANGELOG.md +favicon.svg +.gitattributes +.git/ +.gitignore diff --git a/package.json b/package.json index ae9ee50..097026a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "axette", "description": "Very simple and lightweight AJAX implementation for Nette", - "version": "2.0.3", + "version": "2.0.4", "main": "./dist/axette.cjs", "module": "./dist/axette.mjs", "exports": { diff --git a/src/axette.ts b/src/axette.ts index 2bc4312..ec6bfca 100644 --- a/src/axette.ts +++ b/src/axette.ts @@ -57,6 +57,7 @@ export class Axette { } setSelector(selector: string) { + this.removeOldHandlers(); this.selector = selector; this.init(); } @@ -94,8 +95,46 @@ export class Axette { } } else if (hook && hook.callback !== undefined) { this.hooks.remove(event, hook); - } else { + } else if (hook === undefined) { this.hooks[event] = []; + } else { + throw new TypeError(`Second argument is invalid.`, { cause: hook }); + } + } + + private onLinkClick(e: Event) { + e.preventDefault(); + this.handleAjax((e.currentTarget as HTMLAnchorElement).href); + } + + private onFormSubmit(e: Event) { + e.preventDefault(); + const form = e.currentTarget as HTMLFormElement; + const body = new FormData(form); + if (form.method.toLowerCase() === `post`) { + this.handleAjax(form.action, 'POST', body, {'Content-Type': `application/form-multipart`}, form as Element).catch(err => console.error(err)); + } else { + const formData = new FormData(form); + const params = (new URLSearchParams(String(formData))).toString(); + this.handleAjax(`${form.action}?${params}`).catch(err => console.error(err)); + } + + form.reset(); + } + + private removeOldHandlers() { + const links = document.querySelectorAll(`a${this.selector}`); + if (links) { + links.forEach((link: Element) => { + link.removeEventListener(`click`, this.onLinkClick); + }); + } + + const forms = document.querySelectorAll(`form${this.selector}`) as NodeListOf; + if (forms) { + forms.forEach(form => { + form.removeEventListener(`submit`, this.onFormSubmit); + }); } } @@ -104,34 +143,21 @@ export class Axette { this.hooks.beforeInit.forEach((hook: Hook) => { hook.callback(...hook.args || []); }); + } else { + this.removeOldHandlers(); } const links = document.querySelectorAll(`a${this.selector}`); if (links) { links.forEach((link: Element) => { - link.addEventListener(`click`, (e: Event) => { - e.preventDefault(); - this.handleAjax((e.currentTarget as HTMLAnchorElement).href); - }); + link.addEventListener(`click`, this.onLinkClick); }); } const forms = document.querySelectorAll(`form${this.selector}`) as NodeListOf; if (forms) { forms.forEach(form => { - form.addEventListener(`submit`, (e) => { - e.preventDefault(); - const body = new FormData(form); - if (form.method.toLowerCase() === `post`) { - this.handleAjax(form.action, 'POST', body, {'Content-Type': `application/form-multipart`}, form as Element).catch(err => console.error(err)); - } else { - const formData = new FormData(form); - const params = (new URLSearchParams(String(formData))).toString(); - this.handleAjax(`${form.action}?${params}`).catch(err => console.error(err)); - } - - form.reset(); - }); + form.addEventListener(`submit`, this.onFormSubmit); }); } @@ -167,6 +193,14 @@ export class Axette { window.location.replace(redirect); } Object.entries(snippets).forEach(([id, html]) => { + const snippetEl = document.getElementById(id); + if (!snippetEl) return; + snippetEl.querySelectorAll(`a${this.selector}`).forEach(el => { + el.removeEventListener(`click`, this.onLinkClick); + }); + snippetEl.querySelectorAll(`form${this.selector}`).forEach(el => { + el.removeEventListener(`submit`, this.onFormSubmit); + }); setHtml(id, html as string); });