on master help sign in
Merge pull request #80 from fitzgen/use-cached-in-todomvc

Use Cached<R> in the TodoMVC example

commit1754bd6efb93665e10f563a67633f205e0dfff2b authorNick Fitzgerald <fitzgen@gmail.com> dateMon 20 May 2019, 13:33 (7 years ago) parenta9389c3abed2fe
7 files changed+120−21
Cargo.lock+1 Cargo.toml+1 README.md+9 examples/todomvc/Cargo.toml+1−1 examples/todomvc/src/todo.rs+75−19 examples/todomvc/src/todos.rs+1−1 src/cached.rs+32
Cargo.lock+1
@@ -218,6 +218,7 @@ dependencies = [
218218 "js-sys 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
219219 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
220220 "longest-increasing-subsequence 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
221+ "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)",
221222 "wasm-bindgen 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
222223 "wasm-bindgen-futures 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
223224 "wasm-bindgen-test 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
Cargo.toml+1
@@ -52,6 +52,7 @@ fxhash = "0.2.1"
5252log = { version = "0.4.6", optional = true }
5353bitflags = "1.0.5"
5454longest-increasing-subsequence = "0.1.0"
55+serde = { version = "1.0.91", optional = true }
5556
5657[dependencies.web-sys]
5758version = "0.3.19"
README.md+9
@@ -57,6 +57,15 @@ directory](https://github.com/fitzgen/dodrio/tree/master/examples), including:
5757* [`js-component`](https://github.com/fitzgen/dodrio/tree/master/examples/js-component):
5858 Defines a rendering component in JavaScript with the `dodrio-js-api` crate.
5959
60+## Cargo Features
61+
62+* `log` &mdash; enable debugging-oriented log messages with the `log` crate's
63+ facade. You still have to initialize a logger for the messages to go anywhere,
64+ such as [`console_log`](https://github.com/iamcodemaker/console_log).
65+
66+* `serde` &mdash; enable `serde::{Serialize, Deserialize}` implementations for
67+ `Cached<R>` where `R` is serializable and deserializable.
68+
6069## Design
6170
6271### Bump Allocation
examples/todomvc/Cargo.toml+1−1
@@ -6,7 +6,7 @@ version = "0.1.0"
66
77[dependencies]
88cfg-if = "0.1.9"
9−dodrio = { path = "../.." }
9+dodrio = { path = "../..", features = ["serde"] }
1010futures = "0.1.27"
1111js-sys = "0.3.19"
1212serde = { features = ["derive"], version = "1.0.91" }
examples/todomvc/src/todo.rs+75−19
@@ -1,14 +1,19 @@
11//! Type definition and `dodrio::Render` implementation for a single todo item.
22
33use crate::keys;
4−use dodrio::{Node, Render, RenderContext, RootRender, VdomWeak};
4+use dodrio::{Cached, Node, Render, RenderContext, RootRender, VdomWeak};
55use serde::{Deserialize, Serialize};
66use std::marker::PhantomData;
77use wasm_bindgen::{prelude::*, JsCast};
88
99/// A single todo item.
10−#[derive(Serialize, Deserialize)]
10+#[derive(Default)]
1111pub struct Todo<C> {
12+ inner: Cached<TodoInner<C>>,
13+}
14+
15+#[derive(Serialize, Deserialize)]
16+struct TodoInner<C> {
1217 id: usize,
1318 title: String,
1419 completed: bool,
@@ -48,47 +53,66 @@ impl<C> Todo<C> {
4853 let completed = false;
4954 let edits = None;
5055 Todo {
51− id,
52− title,
53− completed,
54− edits,
55− _controller: PhantomData,
56+ inner: Cached::new(TodoInner {
57+ id,
58+ title,
59+ completed,
60+ edits,
61+ _controller: PhantomData,
62+ }),
5663 }
5764 }
5865
5966 /// Set this todo item's id.
6067 pub fn set_id(&mut self, id: usize) {
61− self.id = id;
68+ if id != self.inner.id {
69+ Cached::invalidate(&self.inner);
70+ }
71+ self.inner.id = id;
6272 }
6373
6474 /// Is this `Todo` complete?
6575 pub fn is_complete(&self) -> bool {
66− self.completed
76+ self.inner.completed
6777 }
6878
6979 /// Mark the `Todo` as complete or not.
7080 pub fn set_complete(&mut self, to: bool) {
71− self.completed = to;
81+ if to != self.inner.completed {
82+ Cached::invalidate(&self.inner);
83+ }
84+ self.inner.completed = to;
7285 }
7386
7487 /// Get this todo's title.
7588 pub fn title(&self) -> &str {
76− &self.title
89+ &self.inner.title
7790 }
7891
7992 /// Set this todo item's title.
8093 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;
8299 }
83100
84101 /// Set the edits for this todo.
85102 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;
87108 }
88109
89110 /// Take this todo's edits, leaving `None` in their place.
90111 pub fn take_edits(&mut self) -> Option<String> {
91− self.edits.take()
112+ if self.inner.edits.is_some() {
113+ Cached::invalidate(&self.inner);
114+ }
115+ self.inner.edits.take()
92116 }
93117}
94118
@@ -99,17 +123,17 @@ impl<C: TodoActions> Render for Todo<C> {
99123 bumpalo::{self, collections::String},
100124 };
101125
102− let id = self.id;
103− let title = self.edits.as_ref().unwrap_or(&self.title);
126+ let id = self.inner.id;
127+ let title = self.inner.edits.as_ref().unwrap_or(&self.inner.title);
104128 let title = bumpalo::format!(in cx.bump, "{}", title).into_bump_str();
105129
106130 li(&cx)
107131 .attr("class", {
108132 let mut class = String::new_in(cx.bump);
109− if self.completed {
133+ if self.inner.completed {
110134 class.push_str("completed ");
111135 }
112− if self.edits.is_some() {
136+ if self.inner.edits.is_some() {
113137 class.push_str("editing");
114138 }
115139 class.into_bump_str()
@@ -121,7 +145,7 @@ impl<C: TodoActions> Render for Todo<C> {
121145 input(&cx)
122146 .attr("class", "toggle")
123147 .attr("type", "checkbox")
124− .bool_attr("checked", self.completed)
148+ .bool_attr("checked", self.inner.completed)
125149 .on("click", move |root, vdom, _event| {
126150 C::toggle_completed(root, vdom, id);
127151 })
@@ -171,3 +195,35 @@ impl<C: TodoActions> Render for Todo<C> {
171195 .finish()
172196 }
173197}
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> {
2828}
2929
3030/// Actions for `Todos` that can be triggered by UI interactions.
31−pub trait TodosActions: TodoActions {
31+pub trait TodosActions: 'static + TodoActions {
3232 /// Toggle the completion state of all todo items.
3333 fn toggle_all(root: &mut dyn RootRender, vdom: VdomWeak);
3434
src/cached.rs+32
@@ -9,6 +9,12 @@ use std::ops::{Deref, DerefMut};
99
1010/// A renderable that supports caching for when rendering is expensive but can
1111/// 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.
1218#[derive(Clone, Debug, Default)]
1319pub struct Cached<R>
1420where
@@ -195,3 +201,29 @@ where
195201 cached.into()
196202 }
197203}
204+
205+#[cfg(feature = "serde")]
206+impl<R> serde::Serialize for Cached<R>
207+where
208+ R: Default + serde::Serialize,
209+{
210+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
211+ where
212+ S: serde::Serializer,
213+ {
214+ self.inner.serialize(serializer)
215+ }
216+}
217+
218+#[cfg(feature = "serde")]
219+impl<'de, R> serde::Deserialize<'de> for Cached<R>
220+where
221+ R: Default + serde::Deserialize<'de>,
222+{
223+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
224+ where
225+ D: serde::Deserializer<'de>,
226+ {
227+ R::deserialize(deserializer).map(Cached::new)
228+ }
229+}