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

Mutation Observer

  1. // Select the node that will be observed for mutations
  2. const targetNode = document.getElementById('some-id');
  3.  
  4. // Options for the observer (which mutations to observe)
  5. const config = { attributes: true, childList: true, subtree: true };
  6.  
  7. // Callback function to execute when mutations are observed
  8. const callback = function(mutationsList, observer) {
  9.     // Use traditional 'for loops' for IE 11 (goodbye IE11!!!!)
  10.     for(const mutation of mutationsList) {
  11.         if (mutation.type === 'childList') {
  12.             console.log('A child node has been added or removed.');
  13.         }
  14.         else if (mutation.type === 'attributes') {
  15.             console.log('The ' + mutation.attributeName + ' attribute was modified.');
  16.         }
  17.     }
  18. };
  19.  
  20. // Create an observer instance linked to the callback function
  21. const observer = new MutationObserver(callback);
  22.  
  23. // Start observing the target node for configured mutations
  24. observer.observe(targetNode, config);
  25.  
  26. // Later, you can stop observing
  27. observer.disconnect();

This is pure gold if you haven’t used it… (from MDN)

// dom // events // graphics // html // javascript // ui

HTML Sanitizer

  1. const unsanitized_string = "abc <script>alert(1)</script> def";  // Unsanitized string of HTML
  2. const sanitizer = new Sanitizer();  // Default sanitizer;
  3.  
  4. // Sanitize the string
  5. let sanitizedDiv = sanitizer.sanitizeFor("div", unsanitized_string);
  6.  
  7. //We can verify the returned element type, and view sanitized HTML in string form:
  8. console.log( (sanitizedDiv instanceof HTMLDivElement) );
  9. // true
  10. console.log(sanitizedDiv.innerHTML)
  11. // "abc  def"
  12.  
  13. // At some point later ...
  14.  
  15. // Get the element to update. This must be a div to match our sanitizeFor() context.
  16. // Set its content to be the children of our sanitized element.
  17. document.querySelector("div#target").replaceChildren(sanitizedDiv.children);

Interesting… From MDN

f = _ => f

  1. f = _ => f
  2.  
  3. f()()()
  4. ()()
  5. ()      () ()
  6.   ()   ()
  7.     ()()
  8.      ()

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

Enum Shorthand

  1. const toEnum = (...values) =>
  2.   Object.freeze(values.reduce((acc, curr) => {
  3.     acc[curr] = curr
  4.     return acc
  5.   }, {}))
  6.  
  7. const Graphics = toEnum(
  8.   'circle', 
  9.   'rect', 
  10.   'triangle'
  11. )
  12.  
  13. console.log(Graphics.circle)

I sure dislike fake enums in js. Something like this at least dries things up – but still, yuk! 😀

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