Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions flash-messages/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Flash Messages Example

This example demonstrates flash messages in LiveTemplate - page-level notifications that show once and clear after each action.

## Running

```bash
cd examples/flash-messages
go run .
```

Then open http://localhost:8080

## Flash Message Types

| Type | Use Case | Style |
|------|----------|-------|
| `success` | Operation completed | Green |
| `error` | Something went wrong | Red |
| `warning` | Caution/duplicate | Yellow |
| `info` | Informational | Blue |

## Setting Flash Messages (Controller)

```go
func (c *Controller) MyAction(state State, ctx *livetemplate.Context) (State, error) {
// Success notification
ctx.SetFlash("success", "Item added successfully!")

// Error notification
ctx.SetFlash("error", "Failed to save changes")

// Warning notification
ctx.SetFlash("warning", "Item already exists")

// Info notification
ctx.SetFlash("info", "Processing complete")

return state, nil
}
```

## Reading Flash Messages (Template)

```html
<!-- Check if any flash exists -->
{{if .lvt.HasAnyFlash}}
<div id="flash-messages">

<!-- Check specific flash type -->
{{if .lvt.HasFlash "success"}}
<div class="alert alert-success">{{.lvt.Flash "success"}}</div>
{{end}}

{{if .lvt.HasFlash "error"}}
<div class="alert alert-error">{{.lvt.Flash "error"}}</div>
{{end}}

</div>
{{end}}
```

## Flash vs Field Errors

| Aspect | Flash Messages | Field Errors |
|--------|----------------|--------------|
| **Purpose** | Page-level notifications | Form field validation |
| **Affects Success** | No | Yes |
| **Template Access** | `.lvt.Flash "key"` | `.lvt.Error "field"` |
| **Lifecycle** | Cleared after render | Cleared on next action |
| **Example** | "Changes saved!" | "Email is required" |

## Key Behaviors

1. **Show Once**: Flash messages are cleared after each action response
2. **Per-Connection**: Not shared across browser tabs
3. **No Persistence**: Don't survive page refresh or WebSocket reconnects
4. **Don't Block Success**: Unlike field errors, flash messages don't set `Success: false`

## Available Template Helpers

| Helper | Description |
|--------|-------------|
| `.lvt.Flash "key"` | Get flash message for key |
| `.lvt.HasFlash "key"` | Check if flash exists for key |
| `.lvt.HasAnyFlash` | Check if any flash messages exist |
| `.lvt.AllFlash` | Get all flash messages as map |
220 changes: 220 additions & 0 deletions flash-messages/flash.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 600px;
margin: 40px auto;
padding: 0 20px;
background: #f5f5f5;
}
h1 { color: #333; }

/* Flash message styles */
.flash {
padding: 12px 16px;
border-radius: 6px;
margin-bottom: 16px;
font-weight: 500;
}
.flash-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.flash-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.flash-warning {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeeba;
}
.flash-info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}

/* Field error styles */
.field-error {
color: #dc3545;
font-size: 14px;
margin-top: 4px;
}

/* Form styles */
.form-group {
margin-bottom: 16px;
}
input[type="text"] {
padding: 10px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 4px;
width: 200px;
}
input.error {
border-color: #dc3545;
}
button {
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 8px;
margin-bottom: 8px;
}
button[type="submit"] {
background: #007bff;
color: white;
}
button[type="submit"]:hover {
background: #0056b3;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-danger:hover {
background: #c82333;
}
.btn-warning {
background: #ffc107;
color: #212529;
}
.btn-warning:hover {
background: #e0a800;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #5a6268;
}

/* List styles */
.item-list {
background: white;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
}
.item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.item:last-child {
border-bottom: none;
}
.item-name {
font-size: 16px;
}
.remove-btn {
padding: 4px 12px;
font-size: 14px;
}

/* Info box */
.info-box {
background: #e9ecef;
padding: 16px;
border-radius: 8px;
margin-top: 24px;
font-size: 14px;
color: #495057;
}
code {
background: #f8f9fa;
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
}
</style>
</head>
<body>
<h1>{{.Title}}</h1>

<!-- Flash Messages Section -->
{{if .lvt.HasAnyFlash}}
<div id="flash-messages">
{{if .lvt.HasFlash "success"}}
<div class="flash flash-success" data-flash="success">{{.lvt.Flash "success"}}</div>
{{end}}
{{if .lvt.HasFlash "error"}}
<div class="flash flash-error" data-flash="error">{{.lvt.Flash "error"}}</div>
{{end}}
{{if .lvt.HasFlash "warning"}}
<div class="flash flash-warning" data-flash="warning">{{.lvt.Flash "warning"}}</div>
{{end}}
{{if .lvt.HasFlash "info"}}
<div class="flash flash-info" data-flash="info">{{.lvt.Flash "info"}}</div>
{{end}}
</div>
{{end}}

<!-- Add Item Form -->
<div class="form-group">
<form lvt-submit="add_item">
<input type="text"
name="item"
placeholder="Enter item name"
class="{{if .lvt.HasError "item"}}error{{end}}" />
<button type="submit">Add Item</button>
{{if .lvt.HasError "item"}}
<div class="field-error">{{.lvt.Error "item"}}</div>
{{end}}
</form>
</div>

<!-- Action Buttons -->
<div class="form-group">
<button lvt-click="clear_items" class="btn-warning">Clear All</button>
<button lvt-click="simulate_error" class="btn-danger">Simulate Error</button>
</div>

<!-- Items List -->
<div class="item-list">
<h3>Items ({{.ItemCount}})</h3>
{{if .Items}}
{{range .Items}}
<div class="item">
<span class="item-name">{{.}}</span>
<button lvt-click="remove_item" lvt-data-item="{{.}}" class="remove-btn btn-secondary">Remove</button>
</div>
{{end}}
{{else}}
<p style="color: #6c757d;">No items. Add some above!</p>
{{end}}
</div>

<!-- Info Box -->
<div class="info-box">
<strong>About Flash Messages:</strong>
<ul>
<li>Flash messages show once and clear after each action</li>
<li>They don't affect <code>ResponseMetadata.Success</code></li>
<li>Types: <code>success</code>, <code>error</code>, <code>warning</code>, <code>info</code></li>
<li>Set via: <code>ctx.SetFlash("success", "message")</code></li>
<li>Read via: <code>.lvt.Flash "success"</code>, <code>.lvt.HasFlash "success"</code></li>
</ul>
</div>

{{if .lvt.DevMode}}
<script src="/livetemplate-client.js"></script>
{{else}}
<script src="https://cdn.jsdelivr.net/npm/@livetemplate/client@latest/dist/livetemplate-client.browser.js"></script>
{{end}}
</body>
</html>
Loading
Loading