To Do List
A simple to-do list app.
To Do List
A simple To Do list app.
Live Demo
Source
package examples
import (
"context"
l "github.com/SamHennessy/hlive"
. "github.com/SamHennessy/hlive/hhtml"
"github.com/SamHennessy/hlive/hlivekit"
)
// ToDoListDemo is a live, interactive instance of the To Do List example,
// served for the "Live Demo" iframe.
func ToDoListDemo() *l.Page {
page := l.NewPage()
page.DOM().Title().Add("To Do Example")
page.DOM().Head().Add(Link(Rel("stylesheet"), Href("https://cdn.simplecss.org/simple.min.css")))
page.DOM().Body().Add(
Header(
H1("To Do App Example"),
P("A simple app where you can add and remove elements"),
),
Main(
newTodoApp().tree,
),
)
return page
}
type todoApp struct {
newTask *l.NodeBox[string]
newTaskInput *l.Component
taskList *hlivekit.ComponentList
tree []l.Tagger
}
func newTodoApp() *todoApp {
app := &todoApp{
newTask: l.Box(""),
}
app.init()
return app
}
func (a *todoApp) init() {
a.initForm()
a.initList()
}
func (a *todoApp) initForm() {
// Forced to a Component (rather than the hhtml Input() tag builder)
// because its input binding is attached after creation.
a.newTaskInput = l.C("input", Type("text"), Placeholder("Task E.g: Buy Food, Walk dog, ..."))
a.newTaskInput.Add(
l.On("input", func(_ context.Context, e l.Event) {
a.newTask.Set(e.Value)
// This is needed to allow us to clear the input on submit
// Without this there would be no difference in the tree to trigger a diff
a.newTaskInput.Add(Value(e.Value))
}),
)
// Forced to a Component (rather than the hhtml Form() tag builder)
// because it's stored in a []l.Tagger, which Adder doesn't satisfy.
f := l.C("form",
l.PreventDefault(),
l.On("submit", func(ctx context.Context, _ l.Event) {
a.addTask(a.newTask.Get())
// Clear input
a.newTask.Set("")
a.newTaskInput.Add(Value(""))
}),
P("Task Label"),
a.newTaskInput, " ",
Button("Add"),
)
a.tree = append(a.tree, f)
}
func (a *todoApp) initList() {
a.taskList = hlivekit.List("div")
// l.T, not the hhtml H3() builder, because a.tree is a []l.Tagger, which
// the hhtml builders' Adder return type doesn't satisfy.
a.tree = append(a.tree,
l.T("h3", "To Do List:"),
a.taskList,
)
}
func (a *todoApp) addTask(label string) {
labelBox := l.Box(label)
// This is a ComponentMountable. This allows the list to do clean up when we remove it.
container := l.CM("div")
labelSpan := Span(labelBox)
// Forced to a Component (rather than the hhtml Input() tag builder)
// because AutoRender is a Component-only field.
labelInput := l.C("input",
Type("text"),
l.AttrsLockBox{"value": labelBox.LockBox},
l.On("input", func(_ context.Context, e l.Event) {
labelBox.Set(e.Value)
}),
)
// Prevent a server side render on each keypress as we don't have anything in the view that updates on keypress of
// this input
labelInput.AutoRender = false
labelForm := Form(
l.PreventDefault(),
l.Style{"display": "none"},
l.On("submit", func(_ context.Context, e l.Event) {
// You can get back to the bound component from the event
lf, ok := e.Binding.Component.(*l.Component)
if !ok {
return
}
lf.Add(l.Style{"display": "none"})
labelSpan.Add(l.StyleOff{"display"})
}),
labelInput, " ",
Button("Update"),
)
container.Add(
// Delete button
Button("🗑️", l.On("click", func(_ context.Context, _ l.Event) {
a.taskList.RemoveItems(container)
})), " ",
// Edit button
Button("✏️", l.On("click", func(_ context.Context, _ l.Event) {
labelSpan.Add(l.Style{"display": "none"})
labelForm.Add(l.StyleOff{"display"})
labelInput.Add(hlivekit.Focus(), l.OnOnce("focus", func(ctx context.Context, _ l.Event) {
hlivekit.FocusRemove(labelInput)
l.Render(ctx)
}))
})), " ",
labelSpan,
labelForm,
)
a.taskList.AddItem(container)
}