Files
leporello-js/docs/examples/todos-preact/index.js

162 lines
3.6 KiB
JavaScript
Raw Normal View History

2023-10-02 03:36:13 +03:00
/*
Example of a TODO app built using the Preact library
2023-10-02 03:36:13 +03:00
*/
import React from "preact/compat"
2023-06-19 07:59:21 +03:00
// Core
// Global application state
let state
// Preserve state (list of TODOs) when editing code
if (globalThis.leporello) {
// Retrieve initial state from Leporello storage
// See: https://github.com/leporello-js/leporello-js?tab=readme-ov-file#saving-state-between-page-reloads
state = globalThis.leporello.storage.get("state")
}
/*
Application logic is structured as pure functions with the signature `(state, ...args) => state`.
This helper function wraps such a function so that its result updates the global state
and can be used as an event handler.
*/
const handler =
fn =>
(...args) => {
state = fn(state, ...args)
if (globalThis.leporello) {
// Persist state to Leporello storage to restore it after page reloads
globalThis.leporello.storage.set("state", state)
}
render()
}
// Higher-order function that injects the current state into a component
const connect = comp => props => comp(props, state)
const render = () => React.render(<App />, document.body)
// Initialize application state if not already restored from storage
if (state == null) {
state = {
todos: [],
text: "",
filter: "ALL",
}
}
window.addEventListener("load", render)
2023-06-19 07:59:21 +03:00
// Components
const App = () => (
<div>
<AddTodo />
<TodoList />
<Footer />
</div>
2023-06-19 07:59:21 +03:00
)
const Footer = () => (
<div>
<span>Show: </span>
<FilterLink filter="ALL">All</FilterLink>
<FilterLink filter="ACTIVE">Active</FilterLink>
<FilterLink filter="COMPLETED">Completed</FilterLink>
</div>
2023-06-19 07:59:21 +03:00
)
const FilterLink = connect(({ filter, children }, state) => {
2023-06-19 07:59:21 +03:00
const disabled = state.filter == filter
return (
<button
onClick={handler(changeFilter.bind(null, filter))}
disabled={disabled}
style={{ marginLeft: "4px" }}
>
{children}
</button>
2023-06-19 07:59:21 +03:00
)
})
2023-06-19 07:59:21 +03:00
const TodoList = connect((_, state) => (
<ul>
{visibleTodos(state).map(todo => (
<Todo key={todo.id} todo={todo} />
))}
</ul>
))
const Todo = ({ todo }) => (
<li
onClick={handler(toggleTodo.bind(null, todo))}
style={{ textDecoration: todo.completed ? "line-through" : "none" }}
>
{todo.text}
</li>
2023-06-19 07:59:21 +03:00
)
const AddTodo = connect((_, state) => {
return (
<div>
<form onSubmit={handler(createTodo)}>
<input value={state.text} onChange={handler(changeText)} autoFocus />
<button type="submit">Add Todo</button>
</form>
</div>
2023-06-19 07:59:21 +03:00
)
})
// Selectors
// Returns a filtered list of TODOs based on the current filter state
2023-06-19 07:59:21 +03:00
function visibleTodos(state) {
if (state.filter == "ALL") {
2023-06-19 07:59:21 +03:00
return state.todos
} else if (state.filter == "ACTIVE") {
2023-06-19 07:59:21 +03:00
return state.todos.filter(t => !t.completed)
} else if (state.filter == "COMPLETED") {
2023-06-19 07:59:21 +03:00
return state.todos.filter(t => t.completed)
} else {
throw new Error("Unknown filter")
2023-06-19 07:59:21 +03:00
}
}
// Reducers
// Updates the input text state
2023-06-19 07:59:21 +03:00
function changeText(state, e) {
return { ...state, text: e.target.value }
2023-06-19 07:59:21 +03:00
}
// Updates the active filter state
2023-06-19 07:59:21 +03:00
function changeFilter(filter, state) {
return { ...state, filter }
2023-06-19 07:59:21 +03:00
}
// Creates a new TODO item if the input text is not empty
2023-06-19 07:59:21 +03:00
function createTodo(state, e) {
e.preventDefault()
if (!state.text.trim()) {
2023-06-19 07:59:21 +03:00
return state
}
return {
...state,
todos: [...state.todos, { text: state.text }],
text: "",
2023-06-19 07:59:21 +03:00
}
}
// Toggles the completion state of a TODO item
2023-06-19 07:59:21 +03:00
function toggleTodo(todo, state) {
return {
...state,
2023-06-19 07:59:21 +03:00
todos: state.todos.map(t =>
t == todo ? { ...todo, completed: !todo.completed } : t,
),
2023-06-19 07:59:21 +03:00
}
}