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

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

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

PointerUp Before Click

  1. document.body.innerHTML += 'click/tap anywhere<br>'
  2.  
  3. document.addEventListener('click', () => {
  4.   document.body.innerHTML += 'click<br>'
  5. })
  6.  
  7. document.addEventListener('pointerup', () => {
  8.   document.body.innerHTML += 'pointerup<br>'
  9. })

pointerup fires before click

Find preventDefault or stopPropagation JavaScript

  1. Event.prototype.preventDefault = () => {
  2.   debugger;  
  3. };
  4.  
  5. // @NOTE `stopPropagation` or `stopImmediatePropagation` could also be 
  6. // preventing events 
  7.  
  8. // Event.prototype.stopPropagation = () => {
  9. //   debugger;  
  10. // };

This is a real life saver – especially for large confusing legacy projects with lots of event logic. You can use this to track down an unwanted preventDefault and/or stopPropagation. I needed this recently to see which of many calls to preventDefault was preventing my touch events from working in a certain area.

I usually pop this into the console and step through things. If you’ve never seen debugger before read about it here…

Track Mouse and Touch Events

  1. const evts = [
  2.   'touchstart', 'touchmove', 'touchend',
  3.   'mousedown', 'mousemove', 'mouseup', 
  4.   'click', 'mousenter', 'mouseleave'
  5. ]
  6. evts.forEach(type => {
  7.   document.addEventListener(type, e => {
  8.     console.log('event: ', type)
  9.   })
  10. })

See which mouse events are able to fire on the document.

I used this recently when fixing some issues with Android Talkback. Screenreaders will swallow mouse events in some cases.

You can add pointer events too if you need them…

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