on master help sign in
Use `Cached` in TodoMVC so that there is an example for users
abed2feNick Fitzgerald
examples/todomvc/Cargo.toml+1−1
@@ -6,7 +6,7 @@ version = "0.1.0"
6
6
7[dependencies]
7[dependencies]
8cfg-if = "0.1.9"
8cfg-if = "0.1.9"
9−dodrio = { path = "../.." }
9+dodrio = { path = "../..", features = ["serde"] }
10futures = "0.1.27"
10futures = "0.1.27"
11js-sys = "0.3.19"
11js-sys = "0.3.19"
12serde = { features = ["derive"], version = "1.0.91" }
12serde = { features = ["derive"], version = "1.0.91" }
examples/todomvc/src/todo.rs+75−19
@@ -1,14 +1,19 @@
1//! Type definition and `dodrio::Render` implementation for a single todo item.
1//! Type definition and `dodrio::Render` implementation for a single todo item.
2
2
3use crate::keys;
3use crate::keys;
4−use dodrio::{Node, Render, RenderContext, RootRender, VdomWeak};
4+use dodrio::{Cached, Node, Render, RenderContext, RootRender, VdomWeak};
5use serde::{Deserialize, Serialize};
5use serde::{Deserialize, Serialize};
6use std::marker::PhantomData;
6use std::marker::PhantomData;
7use wasm_bindgen::{prelude::*, JsCast};
7use wasm_bindgen::{prelude::*, JsCast};
8
8
9/// A single todo item.
9/// A single todo item.
10−#[derive(Serialize, Deserialize)]
10+#[derive(Default)]
11pub struct Todo<C> {
11pub struct Todo<C> {
12+ inner: Cached<TodoInner<C>>,
13+}
14+
15+#[derive(Serialize, Deserialize)]
16+struct TodoInner<C> {
12 id: usize,
17 id: usize,
13 title: String,
18 title: String,
14 completed: bool,
19 completed: bool,
@@ -48,47 +53,66 @@ impl<C> Todo<C> {
48 let completed = false;
53 let completed = false;
49 let edits = None;
54 let edits = None;
50 Todo {
55 Todo {
56+ inner: Cached::new(TodoInner {
51− id,
57+ id,
52− title,
58+ title,
53− completed,
59+ completed,
54− edits,
60+ edits,
55− _controller: PhantomData,
61+ _controller: PhantomData,
62+ }),
56 }
63 }
57 }
64 }
58
65
59 /// Set this todo item's id.
66 /// Set this todo item's id.
60 pub fn set_id(&mut self, id: usize) {
67 pub fn set_id(&mut self, id: usize) {
68+ if id != self.inner.id {
69+ Cached::invalidate(&self.inner);
70+ }
61− self.id = id;
71+ self.inner.id = id;
62 }
72 }
63
73
64 /// Is this `Todo` complete?
74 /// Is this `Todo` complete?
65 pub fn is_complete(&self) -> bool {
75 pub fn is_complete(&self) -> bool {
66− self.completed
76+ self.inner.completed
67 }
77 }
68
78
69 /// Mark the `Todo` as complete or not.
79 /// Mark the `Todo` as complete or not.
70 pub fn set_complete(&mut self, to: bool) {
80 pub fn set_complete(&mut self, to: bool) {
81+ if to != self.inner.completed {
82+ Cached::invalidate(&self.inner);
83+ }
71− self.completed = to;
84+ self.inner.completed = to;
72 }
85 }
73
86
74 /// Get this todo's title.
87 /// Get this todo's title.
75 pub fn title(&self) -> &str {
88 pub fn title(&self) -> &str {
76− &self.title
89+ &self.inner.title
77 }
90 }
78
91
79 /// Set this todo item's title.
92 /// Set this todo item's title.
80 pub fn set_title<S: Into<String>>(&mut self, title: S) {
93 pub fn set_title<S: Into<String>>(&mut self, title: S) {
81− self.title = title.into();
94+ let title = title.into();
95+ if title != self.inner.title {
96+ Cached::invalidate(&self.inner);
97+ }
98+ self.inner.title = title;
82 }
99 }
83
100
84 /// Set the edits for this todo.
101 /// Set the edits for this todo.
85 pub fn set_edits<S: Into<String>>(&mut self, edits: Option<S>) {
102 pub fn set_edits<S: Into<String>>(&mut self, edits: Option<S>) {
86− self.edits = edits.map(Into::into);
103+ let edits = edits.map(Into::into);
104+ if edits != self.inner.edits {
105+ Cached::invalidate(&self.inner);
106+ }
107+ self.inner.edits = edits;
87 }
108 }
88
109
89 /// Take this todo's edits, leaving `None` in their place.
110 /// Take this todo's edits, leaving `None` in their place.
90 pub fn take_edits(&mut self) -> Option<String> {
111 pub fn take_edits(&mut self) -> Option<String> {
112+ if self.inner.edits.is_some() {
113+ Cached::invalidate(&self.inner);
114+ }
91− self.edits.take()
115+ self.inner.edits.take()
92 }
116 }
93}
117}
94
118
@@ -99,17 +123,17 @@ impl<C: TodoActions> Render for Todo<C> {
99 bumpalo::{self, collections::String},
123 bumpalo::{self, collections::String},
100 };
124 };
101
125
102− let id = self.id;
126+ let id = self.inner.id;
103− let title = self.edits.as_ref().unwrap_or(&self.title);
127+ let title = self.inner.edits.as_ref().unwrap_or(&self.inner.title);
104 let title = bumpalo::format!(in cx.bump, "{}", title).into_bump_str();
128 let title = bumpalo::format!(in cx.bump, "{}", title).into_bump_str();
105
129
106 li(&cx)
130 li(&cx)
107 .attr("class", {
131 .attr("class", {
108 let mut class = String::new_in(cx.bump);
132 let mut class = String::new_in(cx.bump);
109− if self.completed {
133+ if self.inner.completed {
110 class.push_str("completed ");
134 class.push_str("completed ");
111 }
135 }
112− if self.edits.is_some() {
136+ if self.inner.edits.is_some() {
113 class.push_str("editing");
137 class.push_str("editing");
114 }
138 }
115 class.into_bump_str()
139 class.into_bump_str()
@@ -121,7 +145,7 @@ impl<C: TodoActions> Render for Todo<C> {
121 input(&cx)
145 input(&cx)
122 .attr("class", "toggle")
146 .attr("class", "toggle")
123 .attr("type", "checkbox")
147 .attr("type", "checkbox")
124− .bool_attr("checked", self.completed)
148+ .bool_attr("checked", self.inner.completed)
125 .on("click", move |root, vdom, _event| {
149 .on("click", move |root, vdom, _event| {
126 C::toggle_completed(root, vdom, id);
150 C::toggle_completed(root, vdom, id);
127 })
151 })
@@ -171,3 +195,35 @@ impl<C: TodoActions> Render for Todo<C> {
171 .finish()
195 .finish()
172 }
196 }
173}
197}
198+
199+impl<C> Default for TodoInner<C> {
200+ fn default() -> Self {
201+ TodoInner {
202+ id: Default::default(),
203+ title: Default::default(),
204+ completed: Default::default(),
205+ edits: Default::default(),
206+ _controller: PhantomData,
207+ }
208+ }
209+}
210+
211+impl<C> serde::Serialize for Todo<C> {
212+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
213+ where
214+ S: serde::Serializer,
215+ {
216+ self.inner.serialize(serializer)
217+ }
218+}
219+
220+impl<'de, C> Deserialize<'de> for Todo<C> {
221+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222+ where
223+ D: serde::Deserializer<'de>,
224+ {
225+ Ok(Todo {
226+ inner: <Cached<TodoInner<C>>>::deserialize(deserializer)?,
227+ })
228+ }
229+}
examples/todomvc/src/todos.rs+1−1
@@ -28,7 +28,7 @@ pub struct Todos<C = Controller> {
28}
28}
29
29
30/// Actions for `Todos` that can be triggered by UI interactions.
30/// Actions for `Todos` that can be triggered by UI interactions.
31−pub trait TodosActions: TodoActions {
31+pub trait TodosActions: 'static + TodoActions {
32 /// Toggle the completion state of all todo items.
32 /// Toggle the completion state of all todo items.
33 fn toggle_all(root: &mut dyn RootRender, vdom: VdomWeak);
33 fn toggle_all(root: &mut dyn RootRender, vdom: VdomWeak);
34
34
src/cached.rs+6
@@ -9,6 +9,12 @@ use std::ops::{Deref, DerefMut};
9
9
10/// A renderable that supports caching for when rendering is expensive but can
10/// A renderable that supports caching for when rendering is expensive but can
11/// generate the same DOM tree.
11/// generate the same DOM tree.
12+///
13+/// # Example
14+///
15+/// The [TodoMVC
16+/// example](https://github.com/fitzgen/dodrio/blob/master/examples/todomvc/src/todo.rs)
17+/// caches individual todo items.
12#[derive(Clone, Debug, Default)]
18#[derive(Clone, Debug, Default)]
13pub struct Cached<R>
19pub struct Cached<R>
14where
20where