We've got a Web App where we have a lot (>50) of little WebComponents that interact with each other.
To keep everything decoupled, we have as a rule that no component can directly reference another. Instead, components fire events which are then (in the "main" app) wired to call another component's methods.
As time went by more and more components where added and the "main" app file became littered with code chunks looking like this:
buttonsToolbar.addEventListener('request-toggle-contact-form-modal', () => {
contactForm.toggle()
})
buttonsToolbar.addEventListener('request-toggle-bug-reporter-modal', () => {
bugReporter.toggle()
})
// ... etc
To ameliorate this we grouped similar functionality together, in a Class
, name it something relevant, pass the participating elements when instantiating and handle the "wiring" within the Class
, like so:
class Contact {
constructor(contactForm, bugReporter, buttonsToolbar) {
this.contactForm = contactForm
this.bugReporterForm = bugReporterForm
this.buttonsToolbar = buttonsToolbar
this.buttonsToolbar
.addEventListener('request-toggle-contact-form-modal', () => {
this.toggleContactForm()
})
this.buttonsToolbar
.addEventListener('request-toggle-bug-reporter-modal', () => {
this.toggleBugReporterForm()
})
}
toggleContactForm() {
this.contactForm.toggle()
}
toggleBugReporterForm() {
this.bugReporterForm.toggle()
}
}
and we instantiate like so:
<html>
<contact-form></contact-form>
<bug-reporter></bug-reporter>
<script>
const contact = new Contact(
document.querySelector('contact-form'),
document.querySelector('bug-form')
)
</script>
</html>
I'm really weary of introducing patterns of my own, especially ones that aren't really OOP-y since I'm using Classes
as mere initialisation containers, for lack of a better word.
Is there a better/more well known defined pattern for handling this type of tasks that I'm missing?
Best Answer
The code you have is pretty good. The thing that seems a bit off-putting is the initialization code is not part of the object itself. That is, you can instantiate an object, but if you forget to call its wiring class, it's useless.
Consider a Notification Center (aka Event Bus) defined something like this:
This is a DIY multi-dispatch event handler. You would then be able to do your own wiring by simply requiring a NotificationCenter as a constructor argument. Sending messages into it and waiting for it to pass you payloads is the only contact you have with the system, so it's very SOLID.
Note: I used in-place string literals for keys to be consistent with the style used in the question and for simplicity. This is not advisable due to risk of typos. Instead, consider using an enumeration or string constants.
In the above code, the Toolbar is responsible for letting the NotificationCenter know what type of events it's interested in, and publishing all of its external interactions via the notify method. Any other class interested in the
toolbar-button-click-event
would simply register for it in its constructor.Interesting variations on this pattern include:
Interesting features include:
Interesting gotchas and possible remedies include: