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) }