@@ -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.
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};
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>>,
15+#[derive(Serialize, Deserialize)]
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;
56+ inner: Cached::new(TodoInner {
53− completed,
59+ completed,
55− _controller: PhantomData,
61+ _controller: PhantomData,
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);
61− self.id = id;
71+ self.inner.id = id;
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
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);
71− self.completed = to;
84+ self.inner.completed = to;
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
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);
98+ self.inner.title = title;
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);
107+ self.inner.edits = edits;
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);
91− self.edits.take()
115+ self.inner.edits.take()
@@ -99,17 +123,17 @@ impl<C: TodoActions> Render for Todo<C> {
99 bumpalo::{self, collections::String},
123 bumpalo::{self, collections::String},
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();
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 ");
112− if self.edits.is_some() {
136+ if self.inner.edits.is_some() {
113 class.push_str("editing");
137 class.push_str("editing");
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);
@@ -171,3 +195,35 @@ impl<C: TodoActions> Render for Todo<C> {
171 .finish()
195 .finish()
199+impl<C> Default for TodoInner<C> {
200+ fn default() -> Self {
202+ id: Default::default(),
203+ title: Default::default(),
204+ completed: Default::default(),
205+ edits: Default::default(),
206+ _controller: PhantomData,
211+impl<C> serde::Serialize for Todo<C> {
212+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
214+ S: serde::Serializer,
216+ self.inner.serialize(serializer)
220+impl<'de, C> Deserialize<'de> for Todo<C> {
221+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
223+ D: serde::Deserializer<'de>,
226+ inner: <Cached<TodoInner<C>>>::deserialize(deserializer)?,