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

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

React Vanilla

  1. <style>*{ font-family: sans-serif; margin-bottoM: .5em;}</style>
  2.  
  3. <h3>TODO</h3>
  4. <ul id=todos></ul>
  5. <label>What needs to be done?<br>
  6.   <input id=todo onchange="newTodo()"/><br>
  7. </label>
  8. <button id=add onclick="newTodo()">Add #1</button>
  9.  
  10. <script>
  11.   let count = 1
  12.   function newTodo() {
  13.     if (todo.value.length > 0) {
  14.       todos.innerHTML += `<li>${todo.value}</li>`
  15.       todo.value = ''
  16.       add.innerText = `Add #${++count}`
  17.     }
  18.   }
  19. </script>

I don’t really like React… Don’t get me wrong, I don’t mind it and I even kind of like using it – there’s something fun about it… But it’s surprising to me that UI work is still so bulky… I think React and most other UI libraries are overly complex… Every now and then I do evil style vanilla js versions of the React homepage examples as a sort of rebellion 😀 This is the React version of the above snippet:

  1. class TodoApp extends React.Component {
  2.   constructor(props) {
  3.     super(props);
  4.     this.state = { items: [], text: '' };
  5.     this.handleChange = this.handleChange.bind(this);
  6.     this.handleSubmit = this.handleSubmit.bind(this);
  7.   }
  8.  
  9.   render() {
  10.     return (
  11.       <div>
  12.         <h3>TODO</h3>
  13.         <TodoList items={this.state.items} />
  14.         <form onSubmit={this.handleSubmit}>
  15.           <label htmlFor="new-todo">
  16.             What needs to be done?
  17.           </label>
  18.           <input
  19.             id="new-todo"
  20.             onChange={this.handleChange}
  21.             value={this.state.text}
  22.           />
  23.           <button>
  24.             Add #{this.state.items.length + 1}
  25.           </button>
  26.         </form>
  27.       </div>
  28.     );
  29.   }
  30.  
  31.   handleChange(e) {
  32.     this.setState({ text: e.target.value });
  33.   }
  34.  
  35.   handleSubmit(e) {
  36.     e.preventDefault();
  37.     if (this.state.text.length === 0) {
  38.       return;
  39.     }
  40.     const newItem = {
  41.       text: this.state.text,
  42.       id: Date.now()
  43.     };
  44.     this.setState(state => ({
  45.       items: state.items.concat(newItem),
  46.       text: ''
  47.     }));
  48.   }
  49. }
  50.  
  51. class TodoList extends React.Component {
  52.   render() {
  53.     return (
  54.       <ul>
  55.         {this.props.items.map(item => (
  56.           <li key={item.id}>{item.text}</li>
  57.         ))}
  58.       </ul>
  59.     );
  60.   }
  61. }
  62.  
  63. root.render(<TodoApp />);
// dom // globals // hacks // humor // ui

Speed Coded Mouse Toy

  1. d = document
  2. b = d.body
  3. b.style.margin = 0
  4. with (b.appendChild(
  5.   Object.assign(d.createElement`canvas`, {
  6.     width: innerWidth,
  7.     height: innerHeight,
  8.   })
  9. ).getContext`2d`) {
  10.   mx = 0
  11.   my = 0
  12.  
  13.   onresize = _ => {
  14.     canvas.width = innerWidth
  15.     canvas.height = innerHeight
  16.   }
  17.  
  18.   onpointermove = e => {
  19.     mx = e.clientX, 
  20.     my = e.clientY
  21.   }
  22.  
  23.   r = 0
  24.   loop = () => {
  25.     save()
  26.     globalCompositeOperation = 'hard-light'
  27.     translate(innerWidth / 2, innerHeight / 2)
  28.  
  29.     rotate(r += .02)
  30.     translate(-innerWidth / 2, -innerHeight / 2)
  31.  
  32.     fillStyle = 'rgba(55, 55, 55, .01)'
  33.     fillRect(0, 0, innerWidth, innerHeight)
  34.  
  35.     fillStyle = 'rgba(116, 196, 221, .02)'
  36.     fillRect(0, my, innerWidth, 20);
  37.  
  38.     fillStyle = 'rgba(255, 255, 255, .02)'
  39.     fillRect(mx, 0, 20, innerHeight);
  40.  
  41.     fillStyle = 'rgba(0, 0, 0, .03)'
  42.     fillRect(0, innerHeight - my, innerWidth, 20);
  43.  
  44.     fillStyle = 'rgba(116, 196, 221, .02)'
  45.     fillRect(innerWidth - mx, 0, 20, innerHeight);
  46.     restore()
  47.     requestAnimationFrame(loop)
  48.   }
  49.   loop()
  50. }

Speed coded mouse toy…

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