on master help sign in
TodoMVC: Use an RAII type to auto render and save to local storage
24743b9Nick Fitzgerald
examples/todomvc/src/controller.rs+83−67
@@ -6,6 +6,7 @@ use crate::todos::{Todos, TodosActions};
6use crate::visibility::Visibility;
6use crate::visibility::Visibility;
7use dodrio::{RootRender, VdomWeak};
7use dodrio::{RootRender, VdomWeak};
8use serde::{Deserialize, Serialize};
8use serde::{Deserialize, Serialize};
9+use std::ops::{Deref, DerefMut};
9
10
10/// The controller for the TodoMVC app.
11/// The controller for the TodoMVC app.
11///
12///
@@ -18,107 +19,122 @@ use serde::{Deserialize, Serialize};
18#[derive(Default, Deserialize, Serialize)]
19#[derive(Default, Deserialize, Serialize)]
19pub struct Controller;
20pub struct Controller;
20
21
21−/// Invoke `f` with the root `Todos` component, save the (likely just modified)
22−/// todos to local storage, and schedule a new `dodrio` render.
23−fn with_todos<F>(root: &mut dyn RootRender, vdom: VdomWeak, f: F)
24−where
25− F: FnOnce(&mut Todos),
26−{
27− let todos = root.unwrap_mut::<Todos>();
28− f(todos);
29− todos.save_to_local_storage();
30− vdom.schedule_render();
31−}
32−
33impl TodosActions for Controller {
22impl TodosActions for Controller {
34 fn toggle_all(root: &mut dyn RootRender, vdom: VdomWeak) {
23 fn toggle_all(root: &mut dyn RootRender, vdom: VdomWeak) {
35− with_todos(root, vdom, |todos| {
24+ let mut todos = AutoCommitTodos::new(root, vdom);
36− let all_complete = todos.todos().iter().all(|t| t.is_complete());
25+ let all_complete = todos.todos().iter().all(|t| t.is_complete());
37− for t in todos.todos_mut() {
26+ for t in todos.todos_mut() {
38− t.set_complete(!all_complete);
27+ t.set_complete(!all_complete);
39− }
40− });
28+ }
41 }
29 }
42
30
43 fn update_draft(root: &mut dyn RootRender, vdom: VdomWeak, draft: String) {
31 fn update_draft(root: &mut dyn RootRender, vdom: VdomWeak, draft: String) {
44− with_todos(root, vdom, |todos| {
32+ let mut todos = AutoCommitTodos::new(root, vdom);
45− todos.set_draft(draft);
33+ todos.set_draft(draft);
46− });
47 }
34 }
48
35
49 fn finish_draft(root: &mut dyn RootRender, vdom: VdomWeak) {
36 fn finish_draft(root: &mut dyn RootRender, vdom: VdomWeak) {
50− with_todos(root, vdom, |todos| {
37+ let mut todos = AutoCommitTodos::new(root, vdom);
51− let title = todos.take_draft();
38+ let title = todos.take_draft();
52− let title = title.trim();
39+ let title = title.trim();
53− if !title.is_empty() {
40+ if !title.is_empty() {
54− let id = todos.todos().len();
41+ let id = todos.todos().len();
55− let new = Todo::new(id, title);
42+ let new = Todo::new(id, title);
56− todos.add_todo(new);
43+ todos.add_todo(new);
57− }
58− });
44+ }
59 }
45 }
60
46
61 fn change_visibility(root: &mut dyn RootRender, vdom: VdomWeak, vis: Visibility) {
47 fn change_visibility(root: &mut dyn RootRender, vdom: VdomWeak, vis: Visibility) {
62− with_todos(root, vdom, |todos| {
48+ let mut todos = AutoCommitTodos::new(root, vdom);
63− todos.set_visibility(vis);
49+ todos.set_visibility(vis);
64− });
65 }
50 }
66
51
67 fn delete_completed(root: &mut dyn RootRender, vdom: VdomWeak) {
52 fn delete_completed(root: &mut dyn RootRender, vdom: VdomWeak) {
68− with_todos(root, vdom, |todos| {
53+ let mut todos = AutoCommitTodos::new(root, vdom);
69− todos.delete_completed();
54+ todos.delete_completed();
70− });
71 }
55 }
72}
56}
73
57
74impl TodoActions for Controller {
58impl TodoActions for Controller {
75 fn toggle_completed(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
59 fn toggle_completed(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
76− with_todos(root, vdom, |todos| {
60+ let mut todos = AutoCommitTodos::new(root, vdom);
77− let t = &mut todos.todos_mut()[id];
61+ let t = &mut todos.todos_mut()[id];
78− let completed = t.is_complete();
62+ let completed = t.is_complete();
79− t.set_complete(!completed);
63+ t.set_complete(!completed);
80− });
81 }
64 }
82
65
83 fn delete(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
66 fn delete(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
84− with_todos(root, vdom, |todos| {
67+ let mut todos = AutoCommitTodos::new(root, vdom);
85− todos.delete_todo(id);
68+ todos.delete_todo(id);
86− });
87 }
69 }
88
70
89 fn begin_editing(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
71 fn begin_editing(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
90− with_todos(root, vdom, |todos| {
72+ let mut todos = AutoCommitTodos::new(root, vdom);
91− let t = &mut todos.todos_mut()[id];
73+ let t = &mut todos.todos_mut()[id];
92− let desc = t.title().to_string();
74+ let desc = t.title().to_string();
93− t.set_edits(Some(desc));
75+ t.set_edits(Some(desc));
94− });
95 }
76 }
96
77
97 fn update_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize, edits: String) {
78 fn update_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize, edits: String) {
98− with_todos(root, vdom, |todos| {
79+ let mut todos = AutoCommitTodos::new(root, vdom);
99− let t = &mut todos.todos_mut()[id];
80+ let t = &mut todos.todos_mut()[id];
100− t.set_edits(Some(edits));
81+ t.set_edits(Some(edits));
101− });
102 }
82 }
103
83
104 fn finish_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
84 fn finish_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
105− with_todos(root, vdom, |todos| {
85+ let mut todos = AutoCommitTodos::new(root, vdom);
106− let t = &mut todos.todos_mut()[id];
86+ let t = &mut todos.todos_mut()[id];
107− if let Some(edits) = t.take_edits() {
87+ if let Some(edits) = t.take_edits() {
108− let edits = edits.trim();
88+ let edits = edits.trim();
109− if edits.is_empty() {
89+ if edits.is_empty() {
110− todos.delete_todo(id);
90+ todos.delete_todo(id);
111− } else {
91+ } else {
112− t.set_title(edits);
92+ t.set_title(edits);
113− }
114 }
93 }
115− });
94+ }
116 }
95 }
117
96
118 fn cancel_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
97 fn cancel_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) {
119− with_todos(root, vdom, |todos| {
98+ let mut todos = AutoCommitTodos::new(root, vdom);
120− let t = &mut todos.todos_mut()[id];
99+ let t = &mut todos.todos_mut()[id];
121− let _ = t.take_edits();
100+ let _ = t.take_edits();
101+ }
102+}
103+
104+/// An RAII type that dereferences to a `Todos` and once it is dropped, saves
105+/// the (presumably just modified) todos to local storage, and schedules a new
106+/// `dodrio` render.
107+pub struct AutoCommitTodos<'a> {
108+ todos: &'a mut Todos,
109+ vdom: VdomWeak,
110+}
111+
112+impl AutoCommitTodos<'_> {
113+ /// Construct a new `AutoCommitTodos` from the root rendering component and
114+ /// `vdom` handle.
115+ pub fn new(root: &mut dyn RootRender, vdom: VdomWeak) -> AutoCommitTodos {
116+ let todos = root.unwrap_mut::<Todos>();
117+ AutoCommitTodos { todos, vdom }
118+ }
122− });
119+}
120+
121+impl Drop for AutoCommitTodos<'_> {
122+ fn drop(&mut self) {
123+ self.todos.save_to_local_storage();
124+ self.vdom.schedule_render();
125+ }
126+}
127+
128+impl Deref for AutoCommitTodos<'_> {
129+ type Target = Todos;
130+
131+ fn deref(&self) -> &Todos {
132+ &self.todos
133+ }
134+}
135+
136+impl DerefMut for AutoCommitTodos<'_> {
137+ fn deref_mut(&mut self) -> &mut Todos {
138+ &mut self.todos
123 }
139 }
124}
140}