TodoList with DML, one of the tiniest implementation 3K gzip
Tags: dml
Published: 2021-08-01 18:52:10

Here is the official demo of TodoList. You can run it live here : 
[TodoList](http://dml-todo.surge.sh/).

Try it on your phone, and see how fast it is.
Features : multilines fields and localstorage.

```html
 
<html lang="de">
 <head>
    <meta charset="utf-8">
    <title>title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://efpage.de/DML/DML_homepage/lib/DML-min.js"></script>
    
 </head>
 <body>
    <script>
let todos = []; // List of todos
const _w = "width: 100%; max-width: 450px;"
const _ba = _top+"min-height: 22px; width: 100%; border: none; resize: none; background-color: rgba(0,0,0,0); font-size: 20px; font-family: sans-serif;"

/*------------------------------------------------------
   count active todos 
  ------------------------------------------------------*/
function countTodos() {
  let _active = 0;
  todos.forEach(function (todo) {if (!todo.cb.checked) _active++;})
  activecount.textContent = "Active: " + _active;
  count.textContent = "Total: " + todos.length
}

/*------------------------------------------------------
   save data
  ------------------------------------------------------*/
function saveData() {
  localStorage.setItem("todocount", todos.length)
  todos.forEach(function (todo, i) {
    localStorage.setItem("todo" + i, todo.ta.value)
    localStorage.setItem("state" + i, todo.cb.checked)
  })
  countTodos()
}

/*------------------------------------------------------
   read old data 
  ------------------------------------------------------*/
function readData(area) {
  let n = localStorage.getItem("todocount");
  for (let i = 0; i < n; i++) {
    let content = localStorage.getItem("todo" + i);
    if (content)
      if (content.trim() != "") {
        let todo = new todoObj(area, content);
        todo.cb.checked = (localStorage.getItem("state" + i) != "false")
        todo.ta.style.height = todo.ta.scrollHeight + "px";
        todo.setState();
      }
  }
  countTodos()
}
/*------------------------------------------------------
  Make new todo Element and clear input 
  get workarea and textarea with content (will be cleared)
  ------------------------------------------------------*/
function mknew(workarea, ta) {
  if (ta.value != "") {
    new todoObj(workarea, ta.value);
    ta.value = '';
    ta.autosize()
  }
  saveData()
  countTodos()
}

/****************************************************************************************
  Class "todo" creates a new todo in area
****************************************************************************************/
class todoObj {
  constructor(area, content) {
    todos.push(this);    // Add Element to todo array

    selectBase(area);    // start inside area
    // Create a todo box
    this.todo = selectBase(div("", "display: flex; position: relative; border-top: solid;  border-width: 1px; "))
    this.cb = checkbox("", "flex: 0 0 30px;align-self: center;");
    this.ta = expandableTextarea(content, _ba); // Create input area

    // Invisible Delete-Button to remove element
    this.bt = button("x", "height: 28px; opacity: 0.0; transition: opacity 0.5s; "
      + " position: absolute; right: 10px; " + _top + _shadow);
    this.bt.onclick = this.delete.bind(this)

    // Set event functions
    this.ta.addEventListener("blur", saveData); // Save data on exit
    this.cb.onclick = () => { this.setState(); saveData(); } // Save data on state change
    this.todo.onmouseenter = () => { this.bt.style.opacity = 1 }
    this.todo.onmouseleave = () => { this.bt.style.opacity = 0 }

    // run once to show state
    this.setState();
    unselectBase(2)
  }
  // Class funcitons:
  /*------------------------------------------------------
     set state color of the input area
    ------------------------------------------------------*/
  setState() {
    this.ta.style.backgroundColor = (this.cb.checked ? "#f0f0e0" : "#ffe");
    this.ta.style.textDecoration = (this.cb.checked ? "line-through" : "none");
  }
  /*------------------------------------------------------
     Delete todo Object
    ------------------------------------------------------*/
  delete() {
    todos = todos.filter(todo => todo != this) // Delete this element from list
    this.todo.remove(); delete (this)
    saveData();
  }
} // end todo class

/****************************************************************************************
  Create page content
****************************************************************************************/

div(h1("todos", "Text-align: center; text-shadow: 2px 2px 4px #444;" + _red), _w);
selectBase(div("", _w + " background-color: #ffe; " + _border + _radius + _bigPadding + _shadow));

// Top Input field
let ta = expandableTextarea("", { "placeholder": "What to do ?", "style":_ba})

// Area for Todos
let workarea = div();     

// footer
hr("margin: 3px auto;")
selectBase(div("", "display: flex;  justify-content: space-between; font-size: 80%;"))
let count = span("count");
let activecount = span("activecount");
unselectBase(2)

/****************************************************************************************
  Events to create new todos
****************************************************************************************/

// Create new Todos on Return or exit
ta.onkeypress = (event) => {
  if (event.which == 13) {
    event.preventDefault();   // Remove CR from input
    mknew(workarea, ta)
  }
}
ta.onblur = () => { mknew(workarea, ta) }

readData(workarea)

/****************************************************************************************
  save Data before unload
****************************************************************************************/
window.onbeforeunload = saveData;
    </script> 
 </body>
</html>

```