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

Map vs WeakMap

  1. const map = new Map()
  2.  
  3. map.set(1, 'one')
  4.  
  5. console.log(map.get(1))
  6.  
  7. const weakMap = new WeakMap()
  8.  
  9. // this will fail with an error:
  10. // weakMap.set(1, 'one')
  11.  
  12. // console.log(weakMap.get(1))

Noticed this gotcha the other day. For some reason WeakMap can’t have integers as keys. After years of using WeakMap I guess I’ve only ever used objects as keys and assumed it could just have keys of any type. Using a Map instead solves the problem, but you’ll need to be careful to manager your references to properly clear the Map. Anyway, just one of those things… Maybe I’ll write more about it when I have some time to dig deeper.

SVG Set Tag MDN

  1. document.body.innerHTML = `
  2. click the rounded rect<br>
  3. <svg viewBox="0 0 10 10" width="150" height="150">
  4.   <style>
  5.     rect { cursor: pointer }
  6.     .round { rx: 5px; fill: green; }
  7.   </style>
  8.  
  9.   <rect id="me" width="10" height="10" rx="3">
  10.     <set attributeName="class" to="round" begin="me.click" dur="2s" />
  11.   </rect>
  12. </svg>
  13. `

This is from MDN. Having never seen or used the set tag before, I thought this was worth a quick post…

// javascript // svg // tricks // ui

Todo MVC (not mvc)

  1. <!DOCTYPE html>
  2. <html lang="en">
  3.   <head>
  4.     <meta charset="utf-8" />
  5.     <title></title>
  6.     <style>
  7.       body {
  8.         background: white;  
  9.       }
  10.       .new-todo {
  11.         padding: 4px;
  12.       }
  13.       .todo {
  14.         border: 1px solid black;
  15.         overflow: hidden;
  16.         background: #efefef;
  17.       }
  18.       .todo-close {
  19.         display: none;
  20.       }
  21.       .todo:hover .todo-close {
  22.         display: block;
  23.       }
  24.       .todo * {
  25.         float: left;
  26.         padding: 10px; 
  27.       }
  28.       .todo-complete {
  29.         background: green;
  30.       }
  31.       .todo-complete .todo-val {
  32.         text-decoration: line-through;
  33.         color: white;
  34.       }
  35.  
  36.       .show-active .todo-complete {
  37.         display: none;
  38.       }
  39.  
  40.       .show-complete .todo:not(.todo-complete) {
  41.         display: none;
  42.       }
  43.       .todo-edit {
  44.         background: gray !important;
  45.         color: black !important;
  46.         text-decoration: none !important;
  47.       }
  48.       .todo-name {
  49.         width: 200px;  
  50.         border: 1px solid gray;
  51.       }
  52.  
  53.       .check-all[data-on="true"] {
  54.         font-weight: bold;
  55.  
  56.         background: darkGray;
  57.       }
  58.     </style>
  59.   </head>
  60.   <body>
  61.     <div class="app">
  62.       <button class="check-all">&#9660;</button>
  63.       <input class="todo-name" type="text" placeholder="What needs to be done?"/>
  64.       <div class="todos"></div>
  65.       <div class="todo-foot">
  66.         <span>
  67.           <span class="todo-num">#</span>
  68.           item<span class="plural">s</span>
  69.           left
  70.         </span>
  71.         <button class="all">all</button>
  72.         <button class="active">active</button>
  73.         <button class="completed">completed</button>
  74.         <button class="clear-completed">clear completed</button>
  75.       </div>
  76.     </div>
  77.  
  78.     <script id="todo" type="text/html">
  79.       <div class="todo">
  80.         <input class="todo-check" type="checkbox" />
  81.         <div class="todo-val">$val</div>
  82.         <button class="todo-close">X</button>
  83.       </div>
  84.     </script> 
  85.  
  86.     <script>
  87.       (function() {  
  88.         var todoName = document.querySelector('.todo-name'),
  89.             todoTmpl = document.querySelector('#todo').innerHTML,
  90.             todos = document.querySelector('.todos'),
  91.             todoFoot = document.querySelector('.todo-foot'),
  92.             todoNum = document.querySelector('.todo-num'),
  93.             plural = document.querySelector('.plural'),
  94.             checkAll = document.querySelector('.check-all'),
  95.             evts = {};
  96.  
  97.         function save() {
  98.           localStorage.todos = todos.innerHTML;
  99.         }
  100.  
  101.         function addTodo(value) {
  102.           if (value.trim().length > 0) {
  103.             todoName.value = '';
  104.             todos.innerHTML += todoTmpl.replace('$val', value);
  105.             generalUpdate();
  106.           }
  107.         }
  108.  
  109.         function updateProp(target, attr, val) {
  110.           val ? target.setAttribute(attr, attr) : target.removeAttribute(attr);
  111.           target[attr] = val;
  112.         }
  113.  
  114.         function setTodoComplete(todo, val) {
  115.           if (val) {
  116.             todo.classList.add('todo-complete');
  117.           } else {
  118.             todo.classList.remove('todo-complete');
  119.             checkAll.setAttribute('data-on', false); 
  120.             localStorage.setItem('allChecked', false);
  121.           }
  122.           updateProp(todo.querySelector('.todo-check'), 'checked', val);
  123.         }
  124.  
  125.         function updateItemsLeft(todoCount) {
  126.           todoNum.innerHTML = todoCount;
  127.           plural.style.display = todoCount === 1 ? 'none' : 'inline';
  128.         }
  129.  
  130.         function handleNoItems(todoCount) {
  131.           todoFoot.style.display = todoCount === 0 ? 'none' : 'block';
  132.         }
  133.  
  134.         function generalUpdate() {
  135.           var todoCount = document.querySelectorAll('.todo').length,
  136.               todoCompleted = document.querySelectorAll('.todo-complete');
  137.           updateItemsLeft(todoCount - todoCompleted.length);
  138.           handleNoItems(todoCount);
  139.           save();
  140.         }
  141.  
  142.         evts['check-all-click'] = function() {
  143.           var todos = document.querySelectorAll('.todo'),
  144.               leng = todos.length,
  145.               val = checkAll.allChecked = !checkAll.allChecked;
  146.           for (var i = 0; i < leng; i++) {
  147.             setTodoComplete(todos[i], val);
  148.           }
  149.           checkAll.setAttribute('data-on', val); 
  150.           localStorage.setItem('allChecked', val);
  151.         };
  152.  
  153.         evts['todo-close-click'] = function(e, todo) {
  154.           todos.removeChild(todo);
  155.         };
  156.  
  157.         evts['todo-check-click'] = function(e, todo) {
  158.           setTodoComplete(todo, e.target.checked);
  159.         };
  160.  
  161.         evts['all-click'] = function() {
  162.           todos.classList.remove('show-active');
  163.           todos.classList.remove('show-complete');
  164.           delete localStorage.mode;
  165.         };
  166.  
  167.         evts['active-click'] = function() {
  168.           evts['all-click']();
  169.           todos.classList.add('show-active');
  170.           localStorage.mode = 'active';
  171.         };
  172.  
  173.         evts['completed-click'] = function() {
  174.           evts['all-click']();
  175.           todos.classList.add('show-complete');
  176.           localStorage.mode = 'completed';
  177.         };
  178.  
  179.         evts['clear-completed-click'] = function(e, todo) {
  180.           var completed = document.querySelectorAll('.todo-complete'), 
  181.               leng = completed.length;
  182.           for (var i = 0; i < leng; i++) {
  183.             completed[i].parentNode.removeChild(completed[i]);
  184.           }
  185.         };
  186.  
  187.         todoName.addEventListener('change', function(e) {
  188.           addTodo(todoName.value);
  189.         });
  190.  
  191.         document.addEventListener('click', function(e) {
  192.           var key = e.target.className + '-click',
  193.               todo = e.target.parentNode;
  194.           if (evts[key] != null) {
  195.             evts[key](e, todo);
  196.             generalUpdate();
  197.           }
  198.         });
  199.  
  200.         function todoBlur(e) {
  201.           e.target.removeEventListener('blur', todoBlur);
  202.           e.target.classList.remove('todo-edit');
  203.           e.target.contentEditable = false;
  204.           if (e.target.innerHTML.trim().length === 0) {
  205.             e.target.parentNode.parentNode.removeChild(e.target.parentNode);  
  206.           }
  207.           save();
  208.         }
  209.  
  210.         document.addEventListener('dblclick', function(e) {
  211.           var range, sel;
  212.           if (e.target.className === 'todo-val') {
  213.             e.target.contentEditable = true;
  214.             e.target.initialVal = e.target.innerHTML;
  215.             e.target.focus();
  216.             e.target.addEventListener('blur', todoBlur);
  217.             e.target.classList.add('todo-edit');
  218.  
  219.             // put cursor at end
  220.             range = document.createRange();
  221.             range.selectNodeContents(e.target);
  222.             range.collapse(false);
  223.             sel = window.getSelection();
  224.             sel.removeAllRanges();
  225.             sel.addRange(range);
  226.           }
  227.         });
  228.  
  229.         document.addEventListener('keydown', function(e) {
  230.           if (document.activeElement.classList.contains('todo-val')) {
  231.             if (e.which === 27) { // ESC
  232.               e.target.innerHTML = e.target.initialVal;
  233.               todoBlur(e);  
  234.             } else if (e.which === 13) { // ENTER
  235.               todoBlur(e);  
  236.             }
  237.           }
  238.         });
  239.  
  240.         document.addEventListener('keyup', function(e) {
  241.           if (document.activeElement.classList.contains('todo-val')) {
  242.             save();
  243.           }
  244.         });
  245.  
  246.         if (localStorage.todos != null) todos.innerHTML = localStorage.todos;
  247.         if (localStorage.allChecked === 'true') evts['check-all-click']();
  248.         if (localStorage.mode != null) evts[localStorage.mode + '-click']();
  249.  
  250.         generalUpdate();
  251.  
  252.       })();
  253.     </script>
  254.   </body>
  255. </html>

I’ve always liked Todo MVC. At some point a few years back I tried to re-create the behavior in a natural vanilla way (whatever was natural to me). The above is what came out at the time. Now, with es6 and some other tricks that I like to use – I think this could be even better… still fun to look at.

NOTE: It is not MVC at all – not even close 😀

// javascript // tricks // ui

TC39

  1. function equals(x: number, y: number): boolean {
  2.     return x === y;
  3. }

Interesting proposal Proposal Types as Comments

Capitalize those types and this would look quite familiar…. cough ES4.

I have nothing against types so 👍 from me.

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

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