NetworkLocations module?
I have several options to handle network locations. First of all, let's assume I am using preferences to store the value.
Basically the code to add a location to the prefs looks like:
function addLocation(location) {
const Services = require("Services");
const PREF = "devtools.aboutdebugging.network-locations";
const locationsAsString = Service.prefs.getStringPref(PREF, "[]");
const locations = JSON.parse(locationsAsString);
locations.push(location);
Services.prefs.setStringPref(JSON.stringify(locations));
}
That's not really code I would like to have in an action or in a reducer, so I want to extract it to a separate module. I have several options to create such a module.
Option 1: create a class that uses actions
The idea would be to have the following api:
constructor(actions) <= pass the actions object so that it can dispatch actions directly
connect() <= explicit method to start listening (here, to preference changes)
addLocation(location)
removeLocation(location)
Here the module is responsible for listening to changes and for dispatching the correct action. The module would be instanciated next to the initialization of the panel (in aboutdebugging.js). In order to call addLocation and removeLocation, we could use the concept of serviceContainer (used in webconsole and netmonitor).
const networkLocations = new NetworkLocations(this.actions);
const serviceContainer = {
// ...
addNetworkLocation: (location) => networkLocations.addLocation(location),
// ...
};
// create the react application, pass the serviceContainer as a prop
render(Provider({ store: this.store }, App({ serviceContainer })), this.mount);
// we need to tell networkLocations that the application has started
// connect() will both listen to preference changes and get the initial value of the
// the preference to dispatch actions.updateNetworkLocations
networkLocations.connect();
Option 2: create a class that emits regular events
Similar to the previous idea, except that instead of dispatching actions, it emits regular events:
constructor() <= no argument we just keep a constructor to start listening to pref changes
on/off(event, listener) <= implements EventEmitter and emits "locations-updated"
addLocation(location)
removeLocation(location)
getLocations()
Similarly, we still need the serviceContainer concept so that components can reach the object and call add/removeLocation. Init code looks almost the same
const networkLocations = new NetworkLocations();
const serviceContainer = {
// ...
addNetworkLocation: (location) => networkLocations.addLocation(location),
// ...
};
// create the react application, pass the serviceContainer as a prop
render(Provider({ store: this.store }, App({ serviceContainer })), this.mount);
this.actions.updateNetworkLocations(networkLocations.getLocations());
networkLocations.on("network-locations-updated", ({ locations }) => {
this.actions.updateLocations(locations);
});
Option 3: just create a static helper
Avoid using an object and just have static helper methods (using more explicit names)
addNetworkLocationsObserver(listener)
addNetworkLocation(location)
removeNetworkLocation(location)
getNetworkLocations()
This is nice because components can just require the module and call add/removeLocation. The initialization code doesn't look too bad either, no more "serviceContainer" needed.
const {getNetworkLocations, addLocationsObserver } = require("./modules/network-locations");
// create the react application
render(Provider({ store: this.store }, App()), this.mount);
this.actions.updateNetworkLocations(getNetworkLocations());
addLocationsObserver(({ locations }) => {
this.actions.updateLocations(locations);
});
I also think it is the only pattern that would allow "actions" to call add/removeLocation, because with option 1 or 2, the "serviceContainer" is only accessible to components. Although a component could always pass the serviceContainer to the action... or make it a global on the window.