all repos — h3 @ 0d4d426f3bb76982050378d573b2956b21af39ec

A tiny, extremely minimalist JavaScript microframework.

Removed App object.
h3rald h3rald@h3rald.com
Fri, 10 Apr 2020 14:57:40 +0200
commit

0d4d426f3bb76982050378d573b2956b21af39ec

parent

c7ba9f3fa54421ae1ef753ad8d49c793189f7440

3 files changed, 146 insertions(+), 154 deletions(-)

jump to
M example/assets/js/app.jsexample/assets/js/app.js

@@ -2,153 +2,152 @@ import h3 from "./h3.js";

import Paginator from "./paginator.js"; import Todo from "./todo.js"; -export default function App(container) { - const self = this; // get the right self... when function are passed as event handlers it changes. - self.container = container; - self.todos = []; - self.filteredTodos = []; - self.view = null; - self.emptyTodoError = false; - self.filter = ""; - self.pagesize = 10; - self.page = 1; +let todos = []; +let filteredTodos = []; +let view = null; +let emptyTodoError = false; +let filter = ""; +let pagesize = 10; +let page = 1; - // State management via localStorage - self.save = () => { - localStorage.setItem("h3_todo_list", JSON.stringify(self.todos)); - }; - self.load = () => { - const todos = localStorage.getItem("h3_todo_list"); - if (todos) { - self.todos = JSON.parse(todos); - } - }; +// State management via localStorage +const save = () => { + localStorage.setItem("h3_todo_list", JSON.stringify(todos)); +}; +const load = () => { + const lsTodos = localStorage.getItem("h3_todo_list"); + if (lsTodos) { + todos = JSON.parse(lsTodos); + } +}; - // Todo component - self.todo = Todo(self); - // Paginator Component - self.paginator = Paginator(self); +// Actual DOM creation/updateing +const update = () => { + save(); + view.update({ vnode: build() }); +}; - // Actual DOM creation/updateing - self.update = () => { - self.save(); - self.view.update({ vnode: self.build() }); - }; +const toggle = (todo) => { + todo.done = !todo.done; + update(); +} +const remove = (todo) => { + todos = todos.filter(({ key }) => key !== todo.key); + update(); +} +// Todo component +const todo = Todo({ toggle, remove }); +// Paginator Component +const paginator = Paginator(update); - // UI Methods - // Add a todo item - self.addTodo = () => { - const newTodo = document.getElementById("new-todo"); - if (!newTodo.value) { - self.emptyTodoError = true; - self.update(); - document.getElementById("new-todo").focus(); - return; - } - self.emptyTodoError = false; - self.todos.unshift({ - key: `todo_${Date.now()}__${newTodo.value}`, // Make todos "unique-enough" to ensure they are processed correctly - text: newTodo.value, - }); - newTodo.value = ""; - self.update(); +// UI Methods +// Add a todo item +const addTodo = () => { + const newTodo = document.getElementById("new-todo"); + if (!newTodo.value) { + emptyTodoError = true; + update(); document.getElementById("new-todo").focus(); - }; + return; + } + emptyTodoError = false; + todos.unshift({ + key: `todo_${Date.now()}__${newTodo.value}`, // Make todos "unique-enough" to ensure they are processed correctly + text: newTodo.value, + }); + newTodo.value = ""; + update(); + document.getElementById("new-todo").focus(); +}; - // Add a todo item when pressing enter in the input field. - self.addTodoOnEnter = (event) => { - if (event.keyCode == 13) { - self.addTodo(); - event.preventDefault(); - } - }; +// Add a todo item when pressing enter in the input field. +const addTodoOnEnter = (event) => { + if (event.keyCode == 13) { + addTodo(); + event.preventDefault(); + } +}; - // Set the todo filter. - self.setFilter = (event) => { - let filter = document.getElementById("filter-text"); - self.filter = filter.value; - self.update(); - filter = document.getElementById("filter-text"); - filter.focus(); - }; +// Set the todo filter. +const setFilter = (event) => { + let f = document.getElementById("filter-text"); + filter = f.value; + update(); + f = document.getElementById("filter-text"); + f.focus(); +}; - // Display todo items - self.displayTodos = () => { - const start = (self.page - 1) * self.pagesize; - const end = Math.min(start + self.pagesize, self.filteredTodos.length); - return self.filteredTodos.slice(start, end).map(self.todo); - }; +// Display todo items +const displayTodos = () => { + const start = (page - 1) * pagesize; + const end = Math.min(start + pagesize, filteredTodos.length); + return filteredTodos.slice(start, end).map(todo); +}; - // Clear error message - self.clearError = () => { - self.emptyTodoError = false; - self.update(); - document.getElementById("new-todo").focus(); - }; +// Clear error message +const clearError = () => { + emptyTodoError = false; + update(); + document.getElementById("new-todo").focus(); +}; - // Filtering function for todo items - self.filterTodos = ({ text }) => text.match(self.filter); +// Filtering function for todo items +const filterTodos = ({ text }) => text.match(filter); - // Main rendering function (creates virtual dom) - self.build = () => { - const hash = window.location.hash; - self.filteredTodos = self.todos.filter(self.filterTodos); - if (hash.match(/page=(\d+)/)) { - self.page = parseInt(hash.match(/page=(\d+)/)[1]); - } - // Recalculate page in case data is filtered. - self.page = - Math.min( - Math.ceil(self.filteredTodos.length / self.pagesize), - self.page - ) || 1; - const emptyTodoErrorClass = self.emptyTodoError ? "" : ".hidden"; - const paginatorData = { - size: self.pagesize, - page: self.page, - total: self.filteredTodos.length, - }; - return h3("div#todolist.todo-list-container", [ - h3("h1", ["To Do List"]), - h3("form.add-todo-form", [ - h3("input", { - id: "new-todo", - placeholder: "What do you want to do?", - autofocus: true, - onkeydown: self.addTodoOnEnter, - }), - h3( - "span.submit-todo", - { - onclick: self.addTodo, - }, - ["+"] - ), - ]), - h3(`div.error${emptyTodoErrorClass}`, [ - h3("span.error-message", ["Please enter a non-empty todo item."]), - h3( - "span.dismiss-error", - { - onclick: self.clearError, - }, - ["✘"] - ), - ]), - h3("div.navigation-bar", [ - h3("input", { - id: "filter-text", - placeholder: "Type to filter todo items...", - onkeyup: self.setFilter, - value: self.filter, - }), - self.paginator(paginatorData), - ]), - h3("div.todo-list", self.displayTodos()), - ]); +// Main rendering function (creates virtual dom) +const build = () => { + const hash = window.location.hash; + filteredTodos = todos.filter(filterTodos); + if (hash.match(/page=(\d+)/)) { + page = parseInt(hash.match(/page=(\d+)/)[1]); + } + // Recalculate page in case data is filtered. + page = Math.min(Math.ceil(filteredTodos.length / pagesize), page) || 1; + const emptyTodoErrorClass = emptyTodoError ? "" : ".hidden"; + const paginatorData = { + size: pagesize, + page: page, + total: filteredTodos.length, }; - self.load(); - self.view = self.build(); - container.appendChild(self.view.render()); -} -new App(document.getElementById("app")); + return h3("div#todolist.todo-list-container", [ + h3("h1", ["To Do List"]), + h3("form.add-todo-form", [ + h3("input", { + id: "new-todo", + placeholder: "What do you want to do?", + autofocus: true, + onkeydown: addTodoOnEnter, + }), + h3( + "span.submit-todo", + { + onclick: addTodo, + }, + ["+"] + ), + ]), + h3(`div.error${emptyTodoErrorClass}`, [ + h3("span.error-message", ["Please enter a non-empty todo item."]), + h3( + "span.dismiss-error", + { + onclick: clearError, + }, + ["✘"] + ), + ]), + h3("div.navigation-bar", [ + h3("input", { + id: "filter-text", + placeholder: "Type to filter todo items...", + onkeyup: setFilter, + value: filter, + }), + paginator(paginatorData), + ]), + h3("div.todo-list", displayTodos()), + ]); +}; +load(); +view = build(); +document.getElementById("app").appendChild(view.render());
M example/assets/js/paginator.jsexample/assets/js/paginator.js

@@ -1,6 +1,6 @@

import h3 from "./h3.js"; -export default function Paginator(app) { +export default function Paginator(update) { return function (data) { let page = data.page; const size = data.size;

@@ -11,12 +11,12 @@ const nextClass = page < pages ? ".link" : ".disabled";

function setPreviousPage() { page = page - 1; window.location.hash = `/?page=${page}`; - app.update(); + update(); } function setNextPage() { page = page + 1; window.location.hash = `/?page=${page}`; - app.update(); + update(); } return h3("div.paginator", [ h3(
M example/assets/js/todo.jsexample/assets/js/todo.js

@@ -1,21 +1,14 @@

-import h3 from './h3.js'; +import h3 from "./h3.js"; -export default function Todo(app) { - return function(data) { +export default function Todo(actions) { + const { toggle, remove } = actions; + return function (data) { const todoStateClass = data.done ? ".done" : ".todo"; - function toggle() { - data.done = !data.done; - app.update(); - } - function remove() { - app.todos = app.todos.filter(({ key }) => key !== data.key); - app.update(); - } return h3("div.todo-item", { id: data.key }, [ h3(`div.todo-content${todoStateClass}`, [ - h3("span.todo-text", { onclick: toggle }, [data.text]), + h3("span.todo-text", { onclick: () => toggle(data) }, [data.text]), ]), - h3("span.delete-todo", { onclick: remove }, ["✘"]), + h3("span.delete-todo", { onclick: () => remove(data) }, ["✘"]), ]); }; }