Two tiers from the same handler. Tier 1 is a plain multipart/form-data form —
it just works, no JavaScript. Tier 2 adds lvt-upload to stream the file in
chunks over the WebSocket and render a live <progress> bar as it arrives.
{{range .lvt.Uploads "chunked-doc"}} exposes per-file progress for the chunked input.
{{define "content"}}
<article>
<h3>File Upload</h3>
<h4>Tier 1: Standard HTML</h4>
<form method="POST" enctype="multipart/form-data" lvt-form:preserve>
<input type="file" name="document">
<button type="submit" name="upload">Upload</button>
</form>
<h4>Tier 2: Chunked with Progress</h4>
<form method="POST" lvt-form:preserve>
<input type="file" lvt-upload="chunked-doc" name="chunked-doc">
{{range .lvt.Uploads "chunked-doc"}}
<div>
<small><strong>{{.ClientName}}</strong> — {{.Progress}}%</small>
<progress value="{{.Progress}}" max="100"></progress>
</div>
{{end}}
<button type="submit" name="upload">Upload</button>
</form>
{{.lvt.FlashTag "success"}}
{{.lvt.FlashTag "error"}}
</article>
{{end}}
WithUpload declares each named upload (size caps, and a small ChunkSize so the
demo's progress is visible); Upload flashes the completed file's name.
type FileUploadController struct{}
func (c *FileUploadController) Upload(state FileUploadState, ctx *livetemplate.Context) (FileUploadState, error) {
for _, name := range []string{"document", "chunked-doc"} {
if ctx.HasUploads(name) {
entries := ctx.GetCompletedUploads(name)
if len(entries) > 0 {
ctx.SetFlash("success", "Uploaded: "+entries[0].ClientName, livetemplate.FlashExpiry(flashSuccessExpiry))
nudgeFlashExpiry(ctx, flashSuccessExpiry)
return state, nil
}
}
}
ctx.SetFlash("error", "No file selected")
return state, nil
}
func (c *FileUploadController) Refresh(state FileUploadState, ctx *livetemplate.Context) (FileUploadState, error) {
return state, nil
}
func fileUploadHandler() http.Handler {
tmpl := newLayoutTmplWithOpts(
[]string{"templates/layout.tmpl", "templates/forms/file-upload.tmpl"},
livetemplate.WithUpload("document", livetemplate.UploadConfig{
MaxFileSize: 10 << 20, // 10 MB
MaxEntries: 1,
}),
livetemplate.WithUpload("chunked-doc", livetemplate.UploadConfig{
MaxFileSize: 10 << 20, // 10 MB
MaxEntries: 1,
ChunkSize: 1024, // 1KB chunks — small so progress is visible for demo files
}),
)
return tmpl.Handle(&FileUploadController{}, livetemplate.AsState(&FileUploadState{
Title: "File Upload",
Category: "Forms & Editing",
}))
}
type FileUploadState struct {
Title string
Category string
}
lvt-upload only when you want progress or
large-file chunking.