Skip to content

Commit 7a6fa4e

Browse files
committed
With this commit, the GUIAdapter now correctly binds UI controls to commands using the newly introduced GUICommandRegistry. This improves the modularity and extensibility of the system by replacing the old CommandFactory with a more centralized approach to command registration.
Key changes: - Introduced `GUICommandRegistry` in `settings.js` to globally register commands before GUI initialization. - Updated `GUIAdapter` to receive `GUICommandRegistry` as a dependency instead of instantiating its own factory. - `bindUIControls()` now logs available commands and correctly binds them to UI elements. - Removed outdated `Command.js` and `CommandFactory.js`, which are now replaced by `GUICommandRegistry`. - Updated `GUIAdapter.test.js` to inject `GUICommandRegistry` for better test isolation. This refactor ensures that commands are managed in a consistent manner across the application, allowing new commands to be added globally without modifying `GUIAdapter` directly.
1 parent 8119f8b commit 7a6fa4e

File tree

9 files changed

+199
-71
lines changed

9 files changed

+199
-71
lines changed

src/config/settings.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { ResistorRenderer } from '../gui/renderers/ResistorRenderer.js';
66
import { WireRenderer } from '../gui/renderers/WireRenderer.js';
77
import { generateId } from '../utils/idGenerator.js';
88
import { Properties } from '../domain/valueObjects/Properties.js';
9+
import { GUICommandRegistry } from "../gui/commands/GUICommandRegistry.js";
10+
import { AddElementCommand } from "../gui/commands/AddElementCommand.js";
11+
912

1013
// Ensure elements are registered once
1114
if (ElementRegistry.getTypes().length === 0) {
@@ -21,10 +24,19 @@ if (ElementRegistry.getTypes().length === 0) {
2124

2225
console.log(" ElementRegistry after registration:", ElementRegistry.getTypes());
2326

27+
// ✅ Register commands globally before GUI initialization
28+
GUICommandRegistry.register("addElement", (circuitService, circuitRenderer, elementRegistry, elementType) =>
29+
new AddElementCommand(circuitService, circuitRenderer, elementRegistry, elementType)
30+
);
31+
32+
console.log("✅ Commands registered:", GUICommandRegistry.getTypes());
33+
console.log("✅ Commands:", GUICommandRegistry._registry);
34+
console.log("✅ Command name:", GUICommandRegistry.get("addElement"));
35+
2436
// Configure RendererFactory
2537
const rendererFactory = new RendererFactory();
2638
rendererFactory.register('resistor', ResistorRenderer);
2739
rendererFactory.register('wire', WireRenderer);
2840

29-
export { ElementRegistry, rendererFactory };
41+
export { ElementRegistry, rendererFactory, GUICommandRegistry };
3042
export default ElementRegistry; // Add default export

src/gui/adapters/GUIAdapter.js

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { CircuitRenderer } from "../renderers/CircuitRenderer.js";
2-
import { CommandFactory } from "../commands/CommandFactory.js";
32
import { CommandHistory } from "../commands/CommandHistory.js";
43

54
/**
@@ -44,15 +43,16 @@ export class GUIAdapter {
4443
* @param {CircuitService} circuitService - The service managing circuit logic.
4544
* @param {Object} elementRegistry - The registry of circuit elements.
4645
* @param {RendererFactory} rendererFactory - The factory for creating element renderers.
46+
* @param {GUICommandRegistry} commandFactory - The factory for creating commands.
4747
*/
48-
constructor(canvas, circuitService, elementRegistry, rendererFactory, commandFactory) {
48+
constructor(canvas, circuitService, elementRegistry, rendererFactory, guiCommandRegistry) {
4949
this.canvas = canvas;
5050
this.circuitService = circuitService;
5151
this.elementRegistry = elementRegistry;
5252
this.circuitRenderer = new CircuitRenderer(canvas, circuitService, rendererFactory);
53-
this.commandFactory = commandFactory;
53+
this.guiCommandRegistry = guiCommandRegistry;
5454

55-
this.commandFactory = new CommandFactory(circuitService, this.circuitRenderer, elementRegistry);
55+
// this.commandFactory = new GUICommandFactory(circuitService, this.circuitRenderer, elementRegistry);
5656
this.commandHistory = new CommandHistory();
5757
}
5858

@@ -61,14 +61,43 @@ export class GUIAdapter {
6161
* Instead of directly modifying circuit state, this now delegates execution to CommandFactory.
6262
*/
6363
bindUIControls() {
64-
this.commandFactory.commands.forEach((createCommand, name) => {
65-
const button = document.getElementById(`button-${name}`);
64+
console.log("🔍 Binding UI controls in GUIAdapter...");
65+
console.log("Commands available:", this.guiCommandRegistry.getTypes());
66+
67+
this.elementRegistry.getTypes().forEach((elementType) => {
68+
const buttonName = `add${elementType}`; // Format should match HTML IDs
69+
console.log(`🔍 Searching for button: ${buttonName}`);
70+
71+
const button = document.getElementById(buttonName);
6672
if (button) {
67-
button.addEventListener("click", () => createCommand().execute());
73+
console.log(`✅ Found button: ${button.id}, binding addElement command for ${elementType}`);
74+
75+
button.addEventListener("click", () => {
76+
console.log(`🛠 Executing addElement command for: ${elementType}`);
77+
78+
// Retrieve the correct command with the element type
79+
const command = this.guiCommandRegistry.get(
80+
"addElement",
81+
// this, // Pass GUIAdapter
82+
this.circuitService,
83+
this.circuitRenderer,
84+
this.elementRegistry,
85+
elementType
86+
);
87+
88+
if (command) {
89+
command.execute();
90+
console.log(`✅ Command 'addElement' executed for ${elementType}`);
91+
} else {
92+
console.warn(`⚠️ Command 'addElement' not found for ${elementType}`);
93+
}
94+
});
95+
} else {
96+
console.warn(`⚠️ Button for adding ${elementType} not found`);
6897
}
6998
});
70-
}
71-
/**
99+
}
100+
/**
72101
* Executes a command by retrieving it from the CommandFactory and executing via CommandHistory.
73102
* @param {string} commandName - The name of the command to execute.
74103
* @param {...any} args - Arguments to pass to the command.

src/gui/commands/AddElementCommand.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { GUICommand } from "./GUICommand.js";
2+
import { Position } from "../../domain/valueObjects/Position.js";
23

34
/**
45
* Command to add an element to the circuit.
@@ -14,7 +15,7 @@ export class AddElementCommand extends GUICommand {
1415
}
1516

1617
execute() {
17-
console.log(`Executing AddElementCommand for ${this.elementType}`);
18+
console.log("Executing AddElementCommand for:", this.elementType);
1819

1920
// Retrieve the factory function for the element
2021
const factory = this.elementRegistry.get(this.elementType);

src/gui/commands/Command.js

Lines changed: 0 additions & 30 deletions
This file was deleted.

src/gui/commands/CommandFactory.js

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
class GUICommandRegistryClass {
2+
constructor() {
3+
if (!GUICommandRegistryClass.instance) {
4+
this._registry = new Map();
5+
GUICommandRegistryClass.instance = this;
6+
}
7+
else {
8+
this._registry = GUICommandRegistryClass.instance._registry; // Ensure continuity
9+
}
10+
return GUICommandRegistryClass.instance;
11+
}
12+
13+
/**
14+
* Registers a command by name.
15+
* @param {string} name - The command name.
16+
* @param {Function} commandFactory - A factory function returning a command instance.
17+
*/
18+
register(name, commandFactory) {
19+
if (this._registry.has(name)) {
20+
throw new Error(`Command "${name}" is already registered.`);
21+
}
22+
this._registry.set(name, commandFactory);
23+
}
24+
25+
/**
26+
* Retrieves a command by name.
27+
* @param {string} name - The command name.
28+
* @returns {Command} - The command instance.
29+
*/
30+
get(name, ...args) {
31+
if (!this._registry.has(name)) {
32+
console.warn(`Command "${name}" not found.`);
33+
return null;
34+
}
35+
console.log(`Calling ${name} with arguments:`, args);
36+
return this._registry.get(name)(...args);
37+
}
38+
39+
/**
40+
* Returns all registered command names.
41+
* @returns {string[]} - List of registered command names.
42+
*/
43+
getTypes() {
44+
return [...this._registry.keys()];
45+
}
46+
}
47+
48+
// Create a Singleton Instance
49+
const GUICommandRegistry = new GUICommandRegistryClass();
50+
Object.freeze( GUICommandRegistry );
51+
export { GUICommandRegistry };

src/gui/main.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import { Circuit } from '../domain/aggregates/Circuit.js';
22
import { CircuitService } from '../application/CircuitService.js';
33
import { GUIAdapter } from './adapters/GUIAdapter.js';
4-
import { ElementRegistry, rendererFactory } from '../config/settings.js'; // Assuming ElementRegistry is configured in settings.js
4+
import { ElementRegistry, rendererFactory, GUICommandRegistry } from '../config/settings.js'; // Assuming ElementRegistry is configured in settings.js
5+
import document from 'document'; // Assuming document is a global object
56

67
// Set up the circuit and services
78
const circuit = new Circuit();
89
const circuitService = new CircuitService(circuit, ElementRegistry);
910

1011
console.log('ElementRegistry:', ElementRegistry);
1112
console.log('rendererFactory:', rendererFactory);
13+
console.log('GUICommandRegistry:', GUICommandRegistry);
1214

1315
// Get the canvas from the DOM
1416
const canvas = document.getElementById('circuitCanvas');
17+
console.log('Canvas:', canvas);
1518

1619
// Create and initialize the GUI Adapter
17-
const guiAdapter = new GUIAdapter(canvas, circuitService, ElementRegistry, rendererFactory);
20+
const guiAdapter = new GUIAdapter(canvas, circuitService, ElementRegistry, rendererFactory, GUICommandRegistry);
1821
guiAdapter.initialize();

src/utils/EventEmitter.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* @file EventEmitter.js
3+
* @description
4+
* A lightweight event system for managing and broadcasting events within the application.
5+
* This class serves as a simplified alternative to Node.js' `EventEmitter`, designed
6+
* to work seamlessly in both **browser** and **Node.js** environments.
7+
*
8+
* **Role in the QuCat Circuit Generator:**
9+
* - Used by `CircuitService` to emit events when elements are added, deleted, or modified.
10+
* - Enables **event-driven UI updates** in `GUIAdapter`, allowing the frontend to react to changes.
11+
* - Provides a **decoupled** way to manage event-driven communication across modules.
12+
*
13+
* **Example Usage:**
14+
* ```js
15+
* const emitter = new EventEmitter();
16+
* emitter.on("elementAdded", (element) => console.log("New element:", element));
17+
* emitter.emit("elementAdded", { id: "R1", type: "resistor" });
18+
* ```
19+
*/
20+
21+
export class EventEmitter {
22+
/**
23+
* Constructs a new EventEmitter instance.
24+
* Manages a collection of event listeners.
25+
*/
26+
constructor() {
27+
/**
28+
* Stores registered event callbacks.
29+
* @type {Object.<string, Function[]>}
30+
*/
31+
this.events = {};
32+
}
33+
34+
/**
35+
* Registers a new listener for the given event.
36+
*
37+
* @param {string} event - The name of the event.
38+
* @param {Function} callback - The function to execute when the event is emitted.
39+
*
40+
* @example
41+
* ```js
42+
* emitter.on("update", () => console.log("Circuit updated"));
43+
* ```
44+
*/
45+
on(event, callback) {
46+
if (!this.events[event]) {
47+
this.events[event] = [];
48+
}
49+
this.events[event].push(callback);
50+
}
51+
52+
/**
53+
* Removes a specific listener from an event.
54+
*
55+
* @param {string} event - The event name.
56+
* @param {Function} callback - The function to remove from the listener list.
57+
*
58+
* @example
59+
* ```js
60+
* const handler = () => console.log("Circuit updated");
61+
* emitter.on("update", handler);
62+
* emitter.off("update", handler); // Removes the listener
63+
* ```
64+
*/
65+
off(event, callback) {
66+
if (this.events[event]) {
67+
this.events[event] = this.events[event].filter(cb => cb !== callback);
68+
}
69+
}
70+
71+
/**
72+
* Emits an event, triggering all registered callbacks for that event.
73+
*
74+
* @param {string} event - The event name.
75+
* @param {*} [data] - Optional data to pass to the event listeners.
76+
*
77+
* @example
78+
* ```js
79+
* emitter.emit("elementAdded", { id: "R1", type: "resistor" });
80+
* ```
81+
*/
82+
emit(event, data) {
83+
if (this.events[event]) {
84+
this.events[event].forEach(callback => callback(data));
85+
}
86+
}
87+
}

tests/gui/GUIAdapter.test.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,18 @@ import { GUIAdapter } from '../../src/gui/adapters/GUIAdapter.js';
66
import { Circuit } from '../../src/domain/aggregates/Circuit.js';
77
import { CircuitService } from '../../src/application/CircuitService.js';
88
import { RendererFactory } from '../../src/gui/renderers/RendererFactory.js';
9-
import { ElementRegistry, rendererFactory } from '../../src/config/settings.js';
9+
import { ElementRegistry, rendererFactory, GUICommandRegistry } from '../../src/config/settings.js';
1010

1111

1212
describe('GUIAdapter Tests', () => {
1313
let canvas;
1414
let guiAdapter;
1515

16-
console.log("🔍 ElementRegistry before test:", ElementRegistry.getTypes());
17-
1816
beforeEach(() => {
19-
console.log("🔍 ElementRegistry before test:", ElementRegistry.getTypes());
2017
setupJsdom();
2118

2219
// Add required buttons to the DOM
23-
['addResistor', 'addWire'].forEach((id) => {
20+
['addResistor', 'addWire', 'addMockElement'].forEach((id) => {
2421
const button = document.createElement('button');
2522
button.id = id;
2623
document.body.appendChild(button);
@@ -47,7 +44,7 @@ describe('GUIAdapter Tests', () => {
4744

4845
const circuit = new Circuit();
4946
const circuitService = new CircuitService(circuit, ElementRegistry);
50-
guiAdapter = new GUIAdapter(canvas, circuitService, ElementRegistry, rendererFactory);
47+
guiAdapter = new GUIAdapter(canvas, circuitService, ElementRegistry, rendererFactory, GUICommandRegistry);
5148
});
5249

5350
it('should initialize without errors', () => {

0 commit comments

Comments
 (0)