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

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

Toggle a Class

  1. // lazy hack for css
  2. document.body.innerHTML = `
  3.   <style> 
  4.     button { 
  5.       cursor: pointer; 
  6.     }
  7.     .red { 
  8.       background-color: red; 
  9.     } 
  10.   </style>
  11. `
  12.  
  13. const btn = document.body.appendChild(
  14.   Object.assign(
  15.     document.createElement('button'), 
  16.     { innerText: 'click me' }
  17.   )
  18. )
  19.  
  20. btn.addEventListener('click', e => {
  21.   e.target.classList.toggle('red')
  22. })

classList.toggle brings back memories…

// css // dom // ui

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

Multiple Class Trick

  1. .thing.thing.thing {
  2.   color: green;
  3. }
  4.  
  5. .thing.thing {
  6.   color: red;
  7. }
  8.  
  9. .thing {
  10.   color: blue;
  11. }

What color would p.thing be?

// css // 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
snippet.zone ~ 2021-24 /// {s/z}