From 9f0f9ae849ccb05b5275fd6049254b071553acb4 Mon Sep 17 00:00:00 2001 From: DAZ Date: Wed, 16 Jul 2025 22:44:22 +0100 Subject: [PATCH 1/5] Adding findElement and findAllElements methods to Controller class --- src/core/controller.ts | 8 +++++ src/core/scope.ts | 28 +++++++++++++-- src/tests/modules/core/find_element_test.ts | 38 +++++++++++++++++++++ 3 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 src/tests/modules/core/find_element_test.ts diff --git a/src/core/controller.ts b/src/core/controller.ts index 4b412716..042da9c1 100644 --- a/src/core/controller.ts +++ b/src/core/controller.ts @@ -73,6 +73,14 @@ export class Controller { return this.scope.data } + findElement(selector: string) { + return this.scope.findElement(selector) + } + + findAllElements(selector: string) { + return this.scope.findAllElements(selector) + } + initialize() { // Override in your subclass to set up initial controller state } diff --git a/src/core/scope.ts b/src/core/scope.ts index de302807..660bf3a2 100644 --- a/src/core/scope.ts +++ b/src/core/scope.ts @@ -26,16 +26,38 @@ export class Scope { } findElement(selector: string): Element | undefined { - return this.element.matches(selector) ? this.element : this.queryElements(selector).find(this.containsElement) + const elementWithId = document.getElementById(selector) + if (elementWithId && this.containsElement(elementWithId)) { + return elementWithId + } + const newSelector = this.classifySelector(selector) + return this.element.matches(newSelector) ? this.element : this.queryElements(newSelector).find(this.containsElement) } findAllElements(selector: string): Element[] { + const newSelector = this.classifySelector(selector) return [ - ...(this.element.matches(selector) ? [this.element] : []), - ...this.queryElements(selector).filter(this.containsElement), + ...(this.element.matches(newSelector) ? [this.element] : []), + ...this.queryElements(newSelector).filter(this.containsElement), ] } + classifySelector(selector: string | string[]): string { + const tokens = Array.isArray(selector) ? selector : [selector] + + const allDefinedTokens = this.getAllClassTokens() + const isDefinedClass = tokens.every((token) => allDefinedTokens.includes(token)) + + const stringySelector = tokens.join(" ") + return isDefinedClass ? `.${stringySelector.replace(/ /g, ".")}` : stringySelector + } + + getAllClassTokens(): string[] { + return Object.entries((this.element as HTMLElement).dataset) + .filter(([key]) => key.endsWith("Class")) + .flatMap(([_, value]) => (value ? value.trim().split(/\s+/) : [])) + } + containsElement = (element: Element): boolean => { return element.closest(this.controllerSelector) === this.element } diff --git a/src/tests/modules/core/find_element_test.ts b/src/tests/modules/core/find_element_test.ts new file mode 100644 index 00000000..1ec28b11 --- /dev/null +++ b/src/tests/modules/core/find_element_test.ts @@ -0,0 +1,38 @@ +import { ClassController } from "../../controllers/class_controller" +import { ControllerTestCase } from "../../cases/controller_test_case" + +export default class FindElementTests extends ControllerTestCase(ClassController) { + fixtureHTML = ` +
+
+
+
+ + "test findElement finds element by id inside scope"() { + const result = this.controller.findElement("inside-id") + this.assert.equal(result?.id, "inside-id") + } + + "test findElement does not find element by id outside scope"() { + const result = this.controller.findElement("outside-id") + this.assert.equal(result, undefined) + } + + "test findElement finds element by defined class"() { + const result = this.controller.findElement(this.controller.loadingClass) + this.assert.ok(result instanceof Element) + this.assert.ok(result?.classList.contains("busy")) + } + + "test findAllElements returns multiple matches for defined class"() { + const results = this.controller.findAllElements(this.controller.loadingClass) + this.assert.ok(Array.isArray(results)) + this.assert.equal(results.length, 1) + this.assert.ok(results[0].classList.contains("busy")) + } + ` +} From c176cc42e7e1707dd4b76667bc9a612f094d9a25 Mon Sep 17 00:00:00 2001 From: Daz Date: Mon, 28 Jul 2025 16:21:42 +0100 Subject: [PATCH 2/5] tidying up tests --- src/tests/modules/core/find_element_test.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/tests/modules/core/find_element_test.ts b/src/tests/modules/core/find_element_test.ts index 1ec28b11..b41d8d56 100644 --- a/src/tests/modules/core/find_element_test.ts +++ b/src/tests/modules/core/find_element_test.ts @@ -4,21 +4,20 @@ import { ControllerTestCase } from "../../cases/controller_test_case" export default class FindElementTests extends ControllerTestCase(ClassController) { fixtureHTML = `
-
+
-
+
"test findElement finds element by id inside scope"() { - const result = this.controller.findElement("inside-id") - this.assert.equal(result?.id, "inside-id") + const result = this.controller.findElement("inside") + this.assert.equal(result?.id, "inside") } "test findElement does not find element by id outside scope"() { - const result = this.controller.findElement("outside-id") + const result = this.controller.findElement("outside") this.assert.equal(result, undefined) } From 60eee054df08de2e4892c1c4fe7efa72778746b6 Mon Sep 17 00:00:00 2001 From: Daz Date: Mon, 28 Jul 2025 17:23:46 +0100 Subject: [PATCH 3/5] made method a bit neater --- src/core/scope.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/core/scope.ts b/src/core/scope.ts index 660bf3a2..6e702ad8 100644 --- a/src/core/scope.ts +++ b/src/core/scope.ts @@ -44,18 +44,15 @@ export class Scope { classifySelector(selector: string | string[]): string { const tokens = Array.isArray(selector) ? selector : [selector] - - const allDefinedTokens = this.getAllClassTokens() - const isDefinedClass = tokens.every((token) => allDefinedTokens.includes(token)) - - const stringySelector = tokens.join(" ") - return isDefinedClass ? `.${stringySelector.replace(/ /g, ".")}` : stringySelector + const definedTokens = new Set(this.getAllClassTokens()) + const allTokensDefined = tokens.every((token) => definedTokens.has(token)) + return tokens.join(allTokensDefined ? "." : " ") } getAllClassTokens(): string[] { return Object.entries((this.element as HTMLElement).dataset) .filter(([key]) => key.endsWith("Class")) - .flatMap(([_, value]) => (value ? value.trim().split(/\s+/) : [])) + .flatMap(([, value]) => value?.trim().split(/\s+/) ?? []) } containsElement = (element: Element): boolean => { From b533b193782b8315d7bd6f39d20cb2068a6fa538 Mon Sep 17 00:00:00 2001 From: Daz Date: Fri, 1 Aug 2025 11:55:37 +0100 Subject: [PATCH 4/5] modify the methods in the controller rather than changing how they are implemented in scope --- src/core/controller.ts | 26 ++++++++++++++++++++++---- src/core/scope.ts | 12 +++--------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/core/controller.ts b/src/core/controller.ts index 042da9c1..c0ff5e3e 100644 --- a/src/core/controller.ts +++ b/src/core/controller.ts @@ -73,12 +73,18 @@ export class Controller { return this.scope.data } - findElement(selector: string) { - return this.scope.findElement(selector) + findElement(selector: string): Element | undefined { + const elementWithId = document.getElementById(selector) + if (elementWithId && this.scope.containsElement(elementWithId)) { + return elementWithId + } + const newSelector = this.classifySelector(selector) + return this.scope.findElement(newSelector) } - findAllElements(selector: string) { - return this.scope.findAllElements(selector) + findAllElements(selector: string): Element[] { + const newSelector = this.classifySelector(selector) + return this.scope.findAllElements(newSelector) } initialize() { @@ -108,4 +114,16 @@ export class Controller { target.dispatchEvent(event) return event } + + private classifySelector(selector: string | string[]): string { + const tokens = Array.isArray(selector) ? selector : [selector] + + const definedClasses: string[] = (this.constructor as any).classes.flatMap((key: string) => { + const value = (this as any)[`${key}Classes`] as string[] | undefined + return value ?? [] + }) + + const allTokensDefined = tokens.every((token) => definedClasses.includes(token)) + return "." + tokens.join(allTokensDefined ? "." : " ") + } } diff --git a/src/core/scope.ts b/src/core/scope.ts index 6e702ad8..fd955ef9 100644 --- a/src/core/scope.ts +++ b/src/core/scope.ts @@ -26,19 +26,13 @@ export class Scope { } findElement(selector: string): Element | undefined { - const elementWithId = document.getElementById(selector) - if (elementWithId && this.containsElement(elementWithId)) { - return elementWithId - } - const newSelector = this.classifySelector(selector) - return this.element.matches(newSelector) ? this.element : this.queryElements(newSelector).find(this.containsElement) + return this.element.matches(selector) ? this.element : this.queryElements(selector).find(this.containsElement) } findAllElements(selector: string): Element[] { - const newSelector = this.classifySelector(selector) return [ - ...(this.element.matches(newSelector) ? [this.element] : []), - ...this.queryElements(newSelector).filter(this.containsElement), + ...(this.element.matches(selector) ? [this.element] : []), + ...this.queryElements(selector).filter(this.containsElement), ] } From 4a82dad41fe9082a03e0b01e2483b1ba97c38302 Mon Sep 17 00:00:00 2001 From: Daz Date: Fri, 1 Aug 2025 11:57:16 +0100 Subject: [PATCH 5/5] remove methods from scope that are not needed --- src/core/scope.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/core/scope.ts b/src/core/scope.ts index fd955ef9..de302807 100644 --- a/src/core/scope.ts +++ b/src/core/scope.ts @@ -36,19 +36,6 @@ export class Scope { ] } - classifySelector(selector: string | string[]): string { - const tokens = Array.isArray(selector) ? selector : [selector] - const definedTokens = new Set(this.getAllClassTokens()) - const allTokensDefined = tokens.every((token) => definedTokens.has(token)) - return tokens.join(allTokensDefined ? "." : " ") - } - - getAllClassTokens(): string[] { - return Object.entries((this.element as HTMLElement).dataset) - .filter(([key]) => key.endsWith("Class")) - .flatMap(([, value]) => value?.trim().split(/\s+/) ?? []) - } - containsElement = (element: Element): boolean => { return element.closest(this.controllerSelector) === this.element }