I want to refactor a number of JS single-page applications that I've programmed not using any particular pattern. Now I've read about interesting frameworks (redux...), but my company is not keen to adopt frameworks in general, everyone here is using vanilla JS. So I want to keep things homemade and as simple as possible. The most obvious defect I find in my old code is the monolithic style, so it seems that introducing component-based architecture with separation of concern would be already a huge improvement. Here is a mock-up I came up with:
let eventGenerator = (function () {
let id = -1;
return {
generate: () => {
++id;
return id;
}
};
}) ();
let dispatcher = (function () {
let components = [];
return {
addComponent: (component) => {
components.push (component);
},
dispatch: (id, detail = null) => {
for (let c of components) {
c.handleEvent (id, detail);
}
}
};
}) ();
const EVT_FAKE_API_RUNNING = eventGenerator.generate ();
const EVT_FAKE_API_SUCCESS = eventGenerator.generate ();
const EVT_FAKE_API_FAILURE = eventGenerator.generate ();
const EVT_FAKE_API_ABORTED = eventGenerator.generate ();
class ComponentFakeAPI {
constructor (param) { // param = nb de secondes à attendre
dispatcher.addComponent (this);
this.param = param;
this.timer = null;
this.result = null;
}
handleEvent (id, detail) {
switch (id) {
case EVT_FETCH_BUTTON_CLICKED:
this.timer = setTimeout (() => {
this.result = Math.round (Math.random () * 100);
if (this.result >= 20)
dispatcher.dispatch (EVT_FAKE_API_SUCCESS, { result: this.result });
else
dispatcher.dispatch (EVT_FAKE_API_FAILURE);
}, this.param);
dispatcher.dispatch (EVT_FAKE_API_RUNNING);
break;
case EVT_ABORT_BUTTON_CLICKED:
clearTimeout (this.timer);
dispatcher.dispatch (EVT_FAKE_API_ABORTED);
}
}
}
const EVT_FETCH_BUTTON_CLICKED = eventGenerator.generate ();
class ComponentFetchButton {
constructor (elt) {
dispatcher.addComponent (this);
elt.innerHTML = `<button>fetch</button>`;
this.elt = elt;
this.but = elt.querySelector ('button');
this.but.onclick = () => dispatcher.dispatch (EVT_FETCH_BUTTON_CLICKED);
}
handleEvent (id, detail) {
switch (id) {
case EVT_FAKE_API_RUNNING:
this.but.disabled = true;
break;
case EVT_FAKE_API_SUCCESS: case EVT_FAKE_API_FAILURE: case EVT_FAKE_API_ABORTED:
this.but.disabled = false;
break;
}
}
}
const EVT_ABORT_BUTTON_CLICKED = eventGenerator.generate ();
class AbortButton {
constructor (elt) {
dispatcher.addComponent (this);
elt.innerHTML = `<button disabled>abort</button>`;
this.elt = elt;
this.but = elt.querySelector ('button');
this.but.onclick = () => dispatcher.dispatch (EVT_ABORT_BUTTON_CLICKED);
}
handleEvent (id, detail) {
switch (id) {
case EVT_FAKE_API_SUCCESS: case EVT_FAKE_API_FAILURE: case EVT_FAKE_API_ABORTED:
this.but.disabled = true;
break;
case EVT_FAKE_API_RUNNING:
this.but.disabled = false;
break;
}
}
}
class ComponentValueDisplay {
constructor (elt) {
dispatcher.addComponent (this);
elt.textContent = '';
this.elt = elt;
}
handleEvent (id, detail) {
switch (id) {
case EVT_FAKE_API_SUCCESS:
this.elt.textContent = detail.result;
break;
case EVT_FAKE_API_FAILURE:
this.elt.textContent = 'failure !';
break;
case EVT_FAKE_API_ABORTED:
this.elt.textContent = 'aborted !';
break;
case EVT_FAKE_API_RUNNING:
this.elt.textContent = '';
break;
}
}
}
class ComponentAverage {
constructor (elt) {
dispatcher.addComponent (this);
elt.textContent = '';
this.elt = elt;
this.sum = 0;
this.avg = 0;
this.n = 0;
}
handleEvent (id, detail) {
switch (id) {
case EVT_FAKE_API_SUCCESS:
++ this.n;
this.sum += detail.result;
this.elt.textContent = Math.round (this.sum / this.n);
break;
}
}
}
window.addEventListener ('load', () => {
let componentFakeAPI = new ComponentFakeAPI (2000);
let componentFetchButton = new ComponentFetchButton (document.querySelector ('#componentFetchButton'));
let componentAbortButton = new AbortButton (document.querySelector ('#componentAbortButton'));
let componentValueDisplay = new ComponentValueDisplay (document.querySelector ('#componentValueDisplay'));
let componentAverage = new ComponentAverage (document.querySelector ('#componentAverage'));
});
#componentValueDisplay, #componentAverage {
margin-left: 10px;
border: 1px solid black;
min-width: 50px;
}
<div style="display: flex">
<div id="componentFetchButton"></div>
<div id="componentAbortButton"></div>
<div>Result</div>
<div id="componentValueDisplay"></div>
<div>Average</div>
<div id="componentAverage"></div>
</div>
I'm wondering if this pattern will hit a wall at some point in a bigger, more complex application. Any advice?