feat(v0): show reusable partial feedback
Add a generated todo notice slot to the v0 normal path so initial render, append, replace, remove, and dynamic Vec<IntoEffect> refreshes visibly compose one reusable TodoRow partial with adjacent generated UI feedback. req: canonical_authoring/003
This commit is contained in:
@@ -11,7 +11,7 @@ Open <http://127.0.0.1:3000>.
|
||||
The page includes working examples for generated target objects and server-first UI commands:
|
||||
|
||||
- counter updates
|
||||
- todo form submission with one `TodoRow` hemplate partial reused by the initial page render, generated row append/replace/remove commands, and dynamic `Vec<T: IntoEffect>` row batches
|
||||
- todo form submission with one `TodoRow` hemplate partial, a notice slot, handlers, generated row append/replace/remove commands, and dynamic `Vec<T: IntoEffect>` row batches reused across initial render and updates
|
||||
- wizard step updates
|
||||
- login form feedback
|
||||
- page swap/navigation
|
||||
|
||||
+29
-10
@@ -125,6 +125,7 @@ impl IntoHandlerFailure for AppError {
|
||||
|
||||
struct Todos {
|
||||
summary: String,
|
||||
notice: String,
|
||||
rows: Vec<TodoRow>,
|
||||
}
|
||||
|
||||
@@ -134,6 +135,8 @@ impl Hemplate for Todos {
|
||||
|
||||
buf.push_str("<section data-hemx-root=\"todos\">\n <form data-hemx-handle=\"add_todo\" data-hemx-form=\"new_todo\">\n <label>New todo\n <input name=\"title\" required=\"required\">\n </label>\n <button type=\"submit\">Add</button>\n <p data-hemx-form-error=\"title\"></p>\n </form>\n\n <p data-hemx-slot=\"summary\">");
|
||||
write!(buf, "{}", hemplate::HtmlEscape(&self.summary))?;
|
||||
buf.push_str("</p>\n <p data-hemx-slot=\"notice\">");
|
||||
write!(buf, "{}", hemplate::HtmlEscape(&self.notice))?;
|
||||
buf.push_str("</p>\n\n <ul data-hemx-slot=\"todo_row\">\n");
|
||||
for row in &self.rows {
|
||||
buf.push_str(" ");
|
||||
@@ -313,6 +316,7 @@ mod todo_handlers {
|
||||
todos::new_todo.error("title", "Title required"),
|
||||
todos::new_todo.focus("title"),
|
||||
todos::summary.set(todo_summary(&state.todos.lock().unwrap())),
|
||||
todos::notice.set("Add a title to create a todo"),
|
||||
));
|
||||
}
|
||||
if form.title.as_str() == "fail-db" {
|
||||
@@ -330,9 +334,10 @@ mod todo_handlers {
|
||||
Ok((
|
||||
todos::todo_row.append(TodoRow {
|
||||
id: TodoId(id),
|
||||
title,
|
||||
title: title.clone(),
|
||||
}),
|
||||
todos::summary.set(summary),
|
||||
todos::notice.set(format!("Added {title}")),
|
||||
todos::new_todo.clear(),
|
||||
))
|
||||
}
|
||||
@@ -403,10 +408,13 @@ fn rename_todo_effect(state: Arc<ExampleState>, form: RenameTodo) -> impl IntoEf
|
||||
.find(|todo| todo.id == form.id.0)
|
||||
.map(|todo| {
|
||||
todo.title = form.title.into_string();
|
||||
todos::todo_row.replace(TodoRow {
|
||||
id: form.id,
|
||||
title: todo.title.clone(),
|
||||
})
|
||||
(
|
||||
todos::todo_row.replace(TodoRow {
|
||||
id: form.id,
|
||||
title: todo.title.clone(),
|
||||
}),
|
||||
todos::notice.set("Todo renamed"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -420,6 +428,7 @@ fn delete_todo_effect(state: Arc<ExampleState>, form: DeleteTodo) -> impl IntoEf
|
||||
(
|
||||
deleted.then(|| todos::todo_row.remove(form.id)),
|
||||
deleted.then(|| todos::summary.set(summary)),
|
||||
deleted.then(|| todos::notice.set("Todo deleted")),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -427,6 +436,7 @@ fn todos_view(todos: &[TodoRecord]) -> Todos {
|
||||
// req: html_safety/002 req: view/001 req: canonical_authoring/003
|
||||
Todos {
|
||||
summary: todo_summary(todos),
|
||||
notice: String::new(),
|
||||
rows: todo_rows(todos),
|
||||
}
|
||||
}
|
||||
@@ -438,7 +448,11 @@ fn refresh_todo_rows_effect(todos: &[TodoRecord]) -> impl IntoEffect {
|
||||
.into_iter()
|
||||
.map(|row| todos::todo_row.replace(row))
|
||||
.collect::<Vec<_>>();
|
||||
(row_updates, todos::summary.set(todo_summary(todos)))
|
||||
(
|
||||
row_updates,
|
||||
todos::summary.set(todo_summary(todos)),
|
||||
todos::notice.set("Todos refreshed"),
|
||||
)
|
||||
}
|
||||
|
||||
fn todo_rows(todos: &[TodoRecord]) -> Vec<TodoRow> {
|
||||
@@ -626,10 +640,11 @@ mod tests {
|
||||
|
||||
let refresh =
|
||||
inspect_batch(refresh_todo_rows_effect(&todos).into_batch(ui::BUILD_FINGERPRINT));
|
||||
assert_eq!(refresh.op_count(), 3);
|
||||
assert_eq!(refresh.op_count(), 4);
|
||||
assert!(refresh.replaces_keyed_html_containing(todos::todo_row, "7", "Ship v0"));
|
||||
assert!(refresh.replaces_keyed_html_containing(todos::todo_row, "8", "Write docs"));
|
||||
assert!(refresh.updates_text(todos::summary));
|
||||
assert!(refresh.updates_text(todos::notice));
|
||||
}
|
||||
|
||||
// req: html_safety/002 req: view/001 req: test/005
|
||||
@@ -688,9 +703,11 @@ mod tests {
|
||||
.batch,
|
||||
);
|
||||
assert_eq!(state.todos.lock().unwrap()[0].title, "Ship v0");
|
||||
assert_eq!(add.op_count(), 3);
|
||||
assert_eq!(add.op_count(), 4);
|
||||
assert!(add.inserts_html_containing(todos::todo_row, "1", "Ship v0"));
|
||||
assert!(add.updates_text(todos::summary));
|
||||
assert!(add.updates_text(todos::notice));
|
||||
assert!(add.payload_contains("Added Ship v0"));
|
||||
assert!(add.resets_form(todos::new_todo));
|
||||
|
||||
let rename = inspect_batch(
|
||||
@@ -704,8 +721,9 @@ mod tests {
|
||||
.batch,
|
||||
);
|
||||
assert_eq!(state.todos.lock().unwrap()[0].title, "Ship 1.0");
|
||||
assert_eq!(rename.op_count(), 1);
|
||||
assert_eq!(rename.op_count(), 2);
|
||||
assert!(rename.replaces_keyed_html_containing(todos::todo_row, "1", "Ship 1.0"));
|
||||
assert!(rename.updates_text(todos::notice));
|
||||
|
||||
let delete = inspect_batch(
|
||||
InteractionRequest::from(form(todo_row::delete_todo, &[("id", "1")]))
|
||||
@@ -715,9 +733,10 @@ mod tests {
|
||||
.batch,
|
||||
);
|
||||
assert!(state.todos.lock().unwrap().is_empty());
|
||||
assert_eq!(delete.op_count(), 2);
|
||||
assert_eq!(delete.op_count(), 3);
|
||||
assert!(delete.removes_key(todos::todo_row, "1"));
|
||||
assert!(delete.updates_text(todos::summary));
|
||||
assert!(delete.updates_text(todos::notice));
|
||||
|
||||
let missing_delete = inspect_batch(
|
||||
InteractionRequest::from(form(todo_row::delete_todo, &[("id", "99")]))
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
<p data-hemx-slot="summary">{+ self.summary +}</p>
|
||||
<p data-hemx-slot="notice">{+ self.notice +}</p>
|
||||
<ul data-hemx-slot="todo_row">
|
||||
<template h-for="row in &self.rows" h-key="row.id">
|
||||
{+ row +}
|
||||
|
||||
Reference in New Issue
Block a user