Skip to content

Instantly share code, notes, and snippets.

@juliandescottes
Created August 9, 2018 15:51
Show Gist options
  • Save juliandescottes/abdb2a262682954452044c044512ebb4 to your computer and use it in GitHub Desktop.
Save juliandescottes/abdb2a262682954452044c044512ebb4 to your computer and use it in GitHub Desktop.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment