)
}
}
)
(
}
{
)
)
(
)
(
(
{
}
)
(
)
}
)
)
{
(
(
)
)
}
)
(
}

Global Messages Observer

  1. const message = ((events = {}) => ({
  2.   on(name, callback) {
  3.     if (!events[name]) events[name] = [];
  4.     events[name].push(callback);
  5.     return message;
  6.   },
  7.  
  8.   off(name, callback) {
  9.     const listeners = events[name];
  10.     if (listeners) {
  11.       for (let i = 0; i < listeners.length; i++) {
  12.         if (listeners[i] === callback) {
  13.           listeners.splice(i, 1);
  14.           break;
  15.         }
  16.       }
  17.     }
  18.   },
  19.  
  20.   trigger(name, data) {
  21.     const listeners = events[name];
  22.     if (listeners) {
  23.       for (let i = 0; i < listeners.length; i++) {
  24.         if (listeners[i]) listeners[i](data);
  25.       }
  26.     } 
  27.   }
  28. }))();
  29.  
  30. message.on('app:begin', () => { 
  31.   console.log('begin...');
  32. });
  33.  
  34. message.on('app:end', () => { 
  35.   console.log('end...');
  36. });
  37.  
  38. message.trigger('app:begin');
  39. message.trigger('app:end');

This snippet or some variation of it is something I use frequently. It allows for easy global events. This is great for quick prototyping and keeping things decoupled. I’ve used it on small, medium and even large projects and apps.

It can be adjusted to for easy debugging in a myriad ways. Here is an example with slightly different coding convention that tracks if an event wasn’t received by a listener and suggest an alternative event name to try:

  1. // function from https://www.tutorialspoint.com/levenshtein-distance-in-javascript
  2. // I just randomly grabbed this... for fun
  3. const levenshteinDistance = (str1 = '', str2 = '') => {
  4.   const track = Array(str2.length + 1)
  5.     .fill(null)
  6.     .map(() => Array(str1.length + 1).fill(null));
  7.   for (let i = 0; i <= str1.length; i++) {
  8.     track[0][i] = i;
  9.   }
  10.   for (let j = 0; j <= str2.length; j++) {
  11.     track[j][0] = j;
  12.   }
  13.   for (let j = 1; j <= str2.length; j++) {
  14.     for (let i = 1; i <= str1.length; i++) {
  15.       const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
  16.       track[j][i] = Math.min(
  17.         track[j][i - 1] + 1, // deletion
  18.         track[j - 1][i] + 1, // insertion
  19.         track[j - 1][i - 1] + indicator // substitution
  20.       );
  21.     }
  22.   }
  23.   return track[str2.length][str1.length];
  24. };
  25.  
  26. const message = (() => {
  27.   const events = {};
  28.   return {
  29.     on(name, callback) {
  30.       if (!events[name]) events[name] = [];
  31.       events[name].push(callback);
  32.       return message;
  33.     },
  34.  
  35.     off(name, callback) {
  36.       const listeners = events[name];
  37.       if (listeners != null) {
  38.         for (let i = 0; i < listeners.length; i++) {
  39.           if (listeners[i] === callback) {
  40.             listeners.splice(i, 1);
  41.             break;
  42.           }
  43.         }
  44.       }
  45.     },
  46.  
  47.     trigger(name, data) {
  48.       // debug only...
  49.       console.log('trigger', name, data);
  50.  
  51.       const listeners = events[name];
  52.       if (listeners != null) {
  53.         for (let i = 0; i < listeners.length; i++) {
  54.           if (listeners[i] != null) listeners[i](data);
  55.         }
  56.       } else {
  57.         // production would never hit this `else`
  58.         // it could even be removed from production in a 
  59.         // number of ways
  60.         console.warn(`no listeners for '${name}'`);
  61.         let matches = [];  
  62.         for (let i in events) {
  63.           matches.push([levenshteinDistance(name, i), name, i]);
  64.         }
  65.         matches.sort((a, b) => a[0] - b[0]);
  66.  
  67.         if (matches[0]) {
  68.           console.warn(`did you mean '${matches[0][2]}' ?`, );
  69.         }
  70.  
  71.         if (matches[1] && matches[1][0] < 4) {
  72.           console.warn(` - or '${matches[1][2]}' ?`);
  73.         }
  74.       }
  75.     },
  76.   };
  77. })();
  78.  
  79. message.on('soda', () => {});
  80. message.on('pop', () => {});
  81.  
  82. message.on('anything', () => {});
  83.  
  84. message.trigger('anythin');
  85.  
  86. // something else that might match
  87. message.on('mything', () => {});
  88.  
  89. message.trigger('anythi ng');

Take a look on the console in the “Try it out…” example.

Anything about the events can easily be tracked and analyzed with little adjustment. You can in theory design a completely decoupled system using this… again, really great for prototyping architectures.

The on and trigger style definitely comes from the days of Backbone, jQuery etc…

Maybe in the future I will make a mock project that uses this to help further explain it

snippet.zone ~ 2021-22 /// {s/z}