all repos — h3 @ cf18fcdb77466464bfe4adc7622efd5ecf740a33

A tiny, extremely minimalist JavaScript microframework.

Refactoring: regions.
h3rald h3rald@h3rald.com
Fri, 10 Apr 2020 19:02:27 +0200
commit

cf18fcdb77466464bfe4adc7622efd5ecf740a33

parent

4eaeb4a491990f91fb316c0349ad12066b7666d8

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

@@ -1,41 +1,24 @@

-import h3, { mount } from "./h3.js"; +import h3, { mount, region } from "./h3.js"; import AddTodoForm from "./components/addTodoForm.js"; import EmptyTodoError from "./components/emptyTodoError.js"; import NavigationBar from "./components/navigationBar.js"; import TodoList from "./components/todoList.js"; +// Main application state let todos = []; let filteredTodos = []; -let app; let displayEmptyTodoError = false; let filter = ""; let pagesize = 10; let page = 1; -// 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); - } -}; - -// Actual DOM creation/updateing -const update = () => { - save(); - app.update({ vnode: build() }); -}; - // UI Methods // Add a todo item -const addTodo = () => { +const addTodo = (updateError) => { const newTodo = document.getElementById("new-todo"); if (!newTodo.value) { displayEmptyTodoError = true; - update(); + updateError(); document.getElementById("new-todo").focus(); return; }

@@ -50,9 +33,9 @@ document.getElementById("new-todo").focus();

}; // Add a todo item when pressing enter in the input field. -const addTodoOnEnter = (event) => { +const addTodoOnEnter = (event, updateError) => { if (event.keyCode == 13) { - addTodo(); + addTodo(updateError); event.preventDefault(); } };

@@ -81,12 +64,17 @@ displayEmptyTodoError = false;

update(); document.getElementById("new-todo").focus(); }; + +const refresh = () => { + update(); +} // Filtering function for todo items const filterTodos = ({ text }) => text.match(filter); // Main rendering function (creates virtual dom) const build = () => { + localStorage.setItem("h3_todo_list", JSON.stringify(todos)); const hash = window.location.hash; filteredTodos = todos.filter(filterTodos); if (hash.match(/page=(\d+)/)) {

@@ -101,14 +89,21 @@ total: filteredTodos.length,

}; const start = (page - 1) * pagesize; const end = Math.min(start + pagesize, filteredTodos.length); + const [error, updateError] = region(() => EmptyTodoError({ displayEmptyTodoError }, { clearError })) return h3("div#todolist.todo-list-container", [ h3("h1", "To Do List"), - AddTodoForm({ addTodo, addTodoOnEnter }), - EmptyTodoError({ displayEmptyTodoError }, { clearError }), - NavigationBar({ filter, paginatorData }, { update, setFilter }), + AddTodoForm({ addTodo, addTodoOnEnter, updateError }), + error, + NavigationBar({ filter, paginatorData }, { refresh, setFilter }), TodoList({ filteredTodos, start, end }, { toggleTodo, removeTodo }), ]); }; -load(); -app = build(); + +const storedTodos = localStorage.getItem("h3_todo_list"); +if (storedTodos) { + todos = JSON.parse(storedTodos); +} + +const [app, update] = region(build); + mount("app", app);
M example/assets/js/components/addTodoForm.jsexample/assets/js/components/addTodoForm.js

@@ -1,18 +1,18 @@

import h3 from "../h3.js"; export default function AddTodoForm(actions) { - const { addTodo, addTodoOnEnter } = actions; + const { addTodo, addTodoOnEnter, updateError, updateMainSection } = actions; return h3("form.add-todo-form", [ h3("input", { id: "new-todo", placeholder: "What do you want to do?", autofocus: true, - onkeydown: addTodoOnEnter, + onkeydown: (event) => addTodoOnEnter(event, updateError, updateMainSection), }), h3( "span.submit-todo", { - onclick: addTodo, + onclick: () => addTodo(updateError, updateMainSection), }, ["+"] ),
M example/assets/js/components/emptyTodoError.jsexample/assets/js/components/emptyTodoError.js

@@ -4,7 +4,7 @@ export default function EmptyTodoError(data, actions) {

const { clearError } = actions; const { displayEmptyTodoError } = data; const emptyTodoErrorClass = displayEmptyTodoError ? "" : ".hidden"; - return h3(`div.error${emptyTodoErrorClass}`, [ + return h3(`div#empty-todo-error.error${emptyTodoErrorClass}`, [ h3("span.error-message", ["Please enter a non-empty todo item."]), h3( "span.dismiss-error",
M example/assets/js/components/navigationBar.jsexample/assets/js/components/navigationBar.js

@@ -3,7 +3,7 @@ import Paginator from "./paginator.js";

export default function NavigationBar(data, actions) { const { filter, paginatorData } = data; - const { setFilter, update } = actions; + const { setFilter, refresh } = actions; return h3("div.navigation-bar", [ h3("input", { id: "filter-text",

@@ -11,6 +11,6 @@ placeholder: "Type to filter todo items...",

onkeyup: setFilter, value: filter, }), - Paginator(paginatorData, { update }), + Paginator(paginatorData, { refresh }), ]); }
M example/assets/js/components/paginator.jsexample/assets/js/components/paginator.js

@@ -1,7 +1,7 @@

import h3 from "../h3.js"; export default function Paginator(data, actions) { - const { update } = actions; + const { refresh } = actions; let page = data.page; const size = data.size; const total = data.total;

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

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

@@ -63,6 +63,14 @@ export const mount = (id, vnode) => {

document.getElementById(id).appendChild(vnode.render()); }; +export const region = (builder) => { + const vnode = builder(); + if (!vnode.id) { + throw new Error("Region VNode does not have an ID."); + } + return [vnode, () => vnode.update({vnode: builder()})] +} + // Virtual Node Implementation with HyperScript-like syntax export class VNode { constructor(...args) {