on master help sign in
examples/todomvc: Use the element builder for rendering
8862ed9Nick Fitzgerald
examples/todomvc/src/lib.rs+2−2
@@ -10,9 +10,9 @@ pub mod todos;
10pub mod utils;
10pub mod utils;
11pub mod visibility;
11pub mod visibility;
12
12
13−use controller::Controller;
13+use crate::controller::Controller;
14+use crate::todos::Todos;
14use dodrio::Vdom;
15use dodrio::Vdom;
15−use todos::Todos;
16use wasm_bindgen::prelude::*;
16use wasm_bindgen::prelude::*;
17
17
18/// Run the TodoMVC app!
18/// Run the TodoMVC app!
examples/todomvc/src/todo.rs+70−124
@@ -1,7 +1,7 @@
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::{bumpalo::Bump, on, Attribute, Node, Render, RootRender, VdomWeak};
4+use dodrio::{bumpalo::Bump, Node, Render, 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};
@@ -93,137 +93,83 @@ impl<C> Todo<C> {
93}
93}
94
94
95impl<C: TodoActions> Render for Todo<C> {
95impl<C: TodoActions> Render for Todo<C> {
96− fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> dodrio::Node<'bump>
96+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
97 where
97 where
98 'a: 'bump,
98 'a: 'bump,
99 {
99 {
100+ use dodrio::{
101+ builder::*,
100− use dodrio::bumpalo::{self, collections::String};
102+ bumpalo::{self, collections::String},
101−
102− let mut class = String::new_in(bump);
103− if self.completed {
104− class.push_str("completed ");
105− }
103+ };
106− if self.edits.is_some() {
107− class.push_str("editing");
108− }
109
104
110 let id = self.id;
105 let id = self.id;
111 let title = self.edits.as_ref().unwrap_or(&self.title);
106 let title = self.edits.as_ref().unwrap_or(&self.title);
112− let elem_id = bumpalo::format!(in bump, "todo-{}", id);
107+
113−
108+ li(bump)
114− let mut input_attrs = bumpalo::vec![
109+ .attr("class", {
115− in bump;
110+ let mut class = String::new_in(bump);
116− Attribute {
111+ if self.completed {
117− name: "class",
112+ class.push_str("completed ");
113+ }
114+ if self.edits.is_some() {
115+ class.push_str("editing");
116+ }
117+ class.into_bump_str()
118+ })
119+ .children([
120+ div(bump)
121+ .attr("class", "view")
122+ .children([
123+ input(bump)
118− value: "toggle",
124+ .attr("class", "toggle")
119− },
120− Attribute {
121− name: "type",
122− value: "checkbox",
125+ .attr("type", "checkbox")
123− }
126+ .bool_attr("checked", self.completed)
124− ];
125− if self.completed {
126− input_attrs.push(Attribute {
127− name: "checked",
128− value: "",
129− });
130− }
131− let input_attrs = input_attrs.into_bump_slice();
132−
133− Node::element(
134− bump,
135− "li",
136− [],
137− [Attribute {
138− name: "class",
139− value: class.into_bump_str(),
140− }],
141− [
142− Node::element(
143− bump,
144− "div",
145− [],
146− [Attribute {
147− name: "class",
148− value: "view",
149− }],
150− [
151− Node::element(
152− bump,
153− "input",
154− [on(bump, "click", move |root, vdom, _event| {
127+ .on("click", move |root, vdom, _event| {
155 C::toggle_completed(root, vdom, id);
128 C::toggle_completed(root, vdom, id);
156− })],
129+ })
157− input_attrs,
130+ .finish(),
158− [],
159− ),
160− Node::element(
161− bump,
162− "label",
131+ label(bump)
163− [on(bump, "dblclick", move |root, vdom, _event| {
132+ .on("dblclick", move |root, vdom, _event| {
164 C::begin_editing(root, vdom, id);
133 C::begin_editing(root, vdom, id);
165− })],
134+ })
166− [],
167− [Node::text(title)],
135+ .children([text(title)])
168− ),
136+ .finish(),
169− Node::element(
170− bump,
171− "button",
137+ button(bump)
138+ .attr("class", "destroy")
172− [on(bump, "click", move |root, vdom, _event| {
139+ .on("click", move |root, vdom, _event| {
173 C::delete(root, vdom, id);
140 C::delete(root, vdom, id);
174− })],
141+ })
175− [Attribute {
142+ .finish(),
143+ ])
144+ .finish(),
145+ input(bump)
176− name: "class",
146+ .attr("class", "edit")
177− value: "destroy",
147+ .attr("value", title)
178− }],
148+ .attr("name", "title")
179− [],
149+ .attr(
180− ),
150+ "id",
181− ],
151+ bumpalo::format!(in bump, "todo-{}", id).into_bump_str(),
182− ),
152+ )
183− Node::element(
184− bump,
185− "input",
186− [
187− on(bump, "input", move |root, vdom, event| {
153+ .on("input", move |root, vdom, event| {
188− let input = event
154+ let input = event
189− .target()
155+ .target()
190− .unwrap_throw()
156+ .unwrap_throw()
191− .unchecked_into::<web_sys::HtmlInputElement>();
157+ .unchecked_into::<web_sys::HtmlInputElement>();
192− C::update_edits(root, vdom, id, input.value());
158+ C::update_edits(root, vdom, id, input.value());
193− }),
159+ })
194− on(bump, "blur", move |root, vdom, _event| {
160+ .on("blur", move |root, vdom, _event| {
195− C::finish_edits(root, vdom, id);
161+ C::finish_edits(root, vdom, id);
196− }),
162+ })
197− on(bump, "keydown", move |root, vdom, event| {
163+ .on("keydown", move |root, vdom, event| {
198− let event = event.unchecked_into::<web_sys::KeyboardEvent>();
164+ let event = event.unchecked_into::<web_sys::KeyboardEvent>();
199− match event.key_code() {
165+ match event.key_code() {
200− keys::ENTER => C::finish_edits(root, vdom, id),
166+ keys::ENTER => C::finish_edits(root, vdom, id),
201− keys::ESCAPE => C::cancel_edits(root, vdom, id),
167+ keys::ESCAPE => C::cancel_edits(root, vdom, id),
202− _ => {}
168+ _ => {}
203− }
204− }),
169+ }
205− ],
206− [
207− Attribute {
208− name: "class",
209− value: "edit",
210− },
170+ })
211− Attribute {
171+ .finish(),
212− name: "value",
213− value: title,
214− },
215− Attribute {
216− name: "name",
217− value: "title",
218− },
219− Attribute {
220− name: "id",
221− value: elem_id.into_bump_str(),
222− },
223− ],
224− [],
225− ),
172+ ])
226− ],
173+ .finish()
227− )
228 }
174 }
229}
175}
examples/todomvc/src/todos.rs+108−242
@@ -7,7 +7,7 @@ use crate::visibility::Visibility;
7use crate::{keys, utils};
7use crate::{keys, utils};
8use dodrio::{
8use dodrio::{
9 bumpalo::{self, Bump},
9 bumpalo::{self, Bump},
10− on, Attribute, Node, Render, RootRender, VdomWeak,
10+ Node, Render, RootRender, VdomWeak,
11};
11};
12use serde::{Deserialize, Serialize};
12use serde::{Deserialize, Serialize};
13use std::marker::PhantomData;
13use std::marker::PhantomData;
@@ -140,65 +140,42 @@ impl<C: TodosActions> Todos<C> {
140 where
140 where
141 'a: 'bump,
141 'a: 'bump,
142 {
142 {
143− Node::element(
143+ use dodrio::builder::*;
144− bump,
144+
145− "header",
145+ header(bump)
146− [],
147− [Attribute {
148− name: "class",
149− value: "header",
146+ .attr("class", "header")
150− }],
147+ .children([
151− [
148+ h1(bump).children([text("todos")]).finish(),
152− Node::element(bump, "h1", [], [], [Node::text("todos")]),
153− Node::element(
154− bump,
149+ input(bump)
155− "input",
156− [
157− on(bump, "input", |root, vdom, event| {
150+ .on("input", |root, vdom, event| {
158− let input = event
151+ let input = event
159− .target()
152+ .target()
160− .unwrap_throw()
153+ .unwrap_throw()
161− .unchecked_into::<web_sys::HtmlInputElement>();
154+ .unchecked_into::<web_sys::HtmlInputElement>();
162− C::update_draft(root, vdom, input.value());
155+ C::update_draft(root, vdom, input.value());
163− }),
156+ })
164− on(bump, "keydown", |root, vdom, event| {
157+ .on("keydown", |root, vdom, event| {
165− let event = event.unchecked_into::<web_sys::KeyboardEvent>();
158+ let event = event.unchecked_into::<web_sys::KeyboardEvent>();
166− if event.key_code() == keys::ENTER {
159+ if event.key_code() == keys::ENTER {
167− C::finish_draft(root, vdom);
160+ C::finish_draft(root, vdom);
168− }
169− }),
161+ }
170− ],
162+ })
171− [
172− Attribute {
173− name: "class",
174− value: "new-todo",
163+ .attr("class", "new-todo")
175− },
176− Attribute {
177− name: "placeholder",
178− value: "What needs to be done?",
164+ .attr("placeholder", "What needs to be done?")
179− },
180− Attribute {
181− name: "autofocus",
165+ .attr("autofocus", "")
182− value: "",
183− },
184− Attribute {
185− name: "value",
186− value: &self.draft,
166+ .attr("value", &self.draft)
187− },
167+ .finish(),
188− ],
189− [],
190− ),
191− ],
168+ ])
192− )
169+ .finish()
193 }
170 }
194
171
195 fn todos_list<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
172 fn todos_list<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
196 where
173 where
197 'a: 'bump,
174 'a: 'bump,
198 {
175 {
199− use dodrio::bumpalo::collections::Vec;
176+ use dodrio::{builder::*, bumpalo::collections::Vec};
200
177
201− let mut todos = Vec::new_in(bump);
178+ let mut todos = Vec::with_capacity_in(self.todos.len(), bump);
202 todos.extend(
179 todos.extend(
203 self.todos
180 self.todos
204 .iter()
181 .iter()
@@ -209,91 +186,43 @@ impl<C: TodosActions> Todos<C> {
209 })
186 })
210 .map(|t| t.render(bump)),
187 .map(|t| t.render(bump)),
211 );
188 );
212− let todos = todos.into_bump_slice();
189+
213−
214− let mut input_attrs = bumpalo::vec![
215− in bump;
216− Attribute {
217− name: "class",
218− value: "toggle-all",
219− },
220− Attribute {
221− name: "id",
222− value: "toggle-all",
223− },
224− Attribute {
225− name: "type",
226− value: "checkbox",
227− },
228− Attribute {
229− name: "name",
230− value: "toggle",
231− }
232− ];
233− if self.todos.iter().all(|t| t.is_complete()) {
234− input_attrs.push(Attribute {
235− name: "checked",
236− value: "",
237− });
238− }
239− let input_attrs = input_attrs.into_bump_slice();
240−
241− Node::element(
242− bump,
243− "section",
190+ section(bump)
244− [],
245− [
246− Attribute {
247− name: "class",
191+ .attr("class", "main")
248− value: "main",
192+ .attr(
249− },
250− Attribute {
251− name: "visibility",
193+ "visibility",
252− value: if self.todos.is_empty() {
194+ if self.todos.is_empty() {
253− "hidden"
195+ "hidden"
254− } else {
196+ } else {
255− "visible"
197+ "visible"
256− },
257 },
198 },
258− ],
199+ )
259− [
200+ .children([
260− Node::element(
261− bump,
262− "input",
201+ input(bump)
202+ .attr("class", "toggle-all")
203+ .attr("id", "toggle-all")
204+ .attr("type", "checkbox")
205+ .attr("name", "toggle")
206+ .bool_attr("checked", self.todos.iter().all(|t| t.is_complete()))
263− [on(bump, "click", |root, vdom, _event| {
207+ .on("click", |root, vdom, _event| {
264 C::toggle_all(root, vdom);
208 C::toggle_all(root, vdom);
265− })],
209+ })
266− input_attrs,
210+ .finish(),
267− [],
268− ),
269− Node::element(
270− bump,
271− "label",
211+ label(bump)
272− [],
273− [Attribute {
274− name: "for",
275− value: "toggle-all",
212+ .attr("for", "toggle-all")
276− }],
277− [Node::text("Mark all as complete")],
213+ .children([text("Mark all as complete")])
278− ),
214+ .finish(),
279− Node::element(
215+ ul(bump).attr("class", "todo-list").children(todos).finish(),
280− bump,
281− "ul",
282− [],
283− [Attribute {
284− name: "class",
285− value: "todo-list",
286− }],
287− todos,
288− ),
289− ],
216+ ])
290− )
217+ .finish()
291 }
218 }
292
219
293 fn footer<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
220 fn footer<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
294 where
221 where
295 'a: 'bump,
222 'a: 'bump,
296 {
223 {
224+ use dodrio::builder::*;
225+
297 let completed_count = self.todos.iter().filter(|t| t.is_complete()).count();
226 let completed_count = self.todos.iter().filter(|t| t.is_complete()).count();
298 let incomplete_count = self.todos.len() - completed_count;
227 let incomplete_count = self.todos.len() - completed_count;
299 let items_left = if incomplete_count == 1 {
228 let items_left = if incomplete_count == 1 {
@@ -303,95 +232,43 @@ impl<C: TodosActions> Todos<C> {
303 };
232 };
304 let incomplete_count = bumpalo::format!(in bump, "{}", incomplete_count);
233 let incomplete_count = bumpalo::format!(in bump, "{}", incomplete_count);
305
234
306− let attrs = if self.todos.is_empty() {
307− &bump.alloc([
308− Attribute {
309− name: "class",
310− value: "footer",
311− },
312− Attribute {
313− name: "hidden",
314− value: "",
315− },
316− ])[..]
317− } else {
318− &bump.alloc([Attribute {
319− name: "class",
320− value: "footer",
321− }])[..]
322− };
323−
324 let clear_completed_text = bumpalo::format!(
235 let clear_completed_text = bumpalo::format!(
325 in bump,
236 in bump,
326 "Clear completed ({})",
237 "Clear completed ({})",
327 self.todos.iter().filter(|t| t.is_complete()).count()
238 self.todos.iter().filter(|t| t.is_complete()).count()
328 );
239 );
329
240
330− Node::element(
331− bump,
241+ footer(bump)
332− "footer",
242+ .attr("class", "footer")
333− [],
243+ .bool_attr("hidden", self.todos.is_empty())
334− attrs,
244+ .children([
335− [
336− Node::element(
337− bump,
338− "span",
245+ span(bump)
339− [],
340− [Attribute {
341− name: "class",
342− value: "todo-count",
246+ .attr("class", "todo-count")
343− }],
247+ .children([
344− [
345− Node::element(
346− bump,
347− "strong",
248+ strong(bump)
348− [],
349− [],
350− [Node::text(incomplete_count.into_bump_str())],
249+ .children([text(incomplete_count.into_bump_str())])
351− ),
250+ .finish(),
352− Node::text(items_left),
251+ text(items_left),
353− ],
252+ ])
354− ),
253+ .finish(),
355− Node::element(
356− bump,
357− "ul",
254+ ul(bump)
358− [],
359− [Attribute {
360− name: "class",
361− value: "filters",
255+ .attr("class", "filters")
362− }],
256+ .children([
363− [
364 self.visibility_swap(bump, "#/", Visibility::All),
257 self.visibility_swap(bump, "#/", Visibility::All),
365 self.visibility_swap(bump, "#/active", Visibility::Active),
258 self.visibility_swap(bump, "#/active", Visibility::Active),
366 self.visibility_swap(bump, "#/completed", Visibility::Completed),
259 self.visibility_swap(bump, "#/completed", Visibility::Completed),
367− ],
260+ ])
368− ),
261+ .finish(),
369− Node::element(
370− bump,
371− "button",
262+ button(bump)
372− [on(bump, "click", |root, vdom, _event| {
263+ .on("click", |root, vdom, _event| {
373 C::delete_completed(root, vdom);
264 C::delete_completed(root, vdom);
374− })],
265+ })
375− {
376− let mut attrs = bumpalo::vec![
377− in bump;
378− Attribute {
379− name: "class",
380− value: "clear-completed",
266+ .attr("class", "clear-completed")
381− }
267+ .bool_attr("hidden", completed_count == 0)
382− ];
383− if self.todos.iter().all(|t| !t.is_complete()) {
384− attrs.push(Attribute {
385− name: "hidden",
386− value: "",
387− });
388− }
389− attrs
390− },
391− [Node::text(clear_completed_text.into_bump_str())],
268+ .children([text(clear_completed_text.into_bump_str())])
392− ),
269+ .finish(),
393− ],
270+ ])
394− )
271+ .finish()
395 }
272 }
396
273
397 fn visibility_swap<'a, 'bump>(
274 fn visibility_swap<'a, 'bump>(
@@ -403,34 +280,25 @@ impl<C: TodosActions> Todos<C> {
403 where
280 where
404 'a: 'bump,
281 'a: 'bump,
405 {
282 {
406− Node::element(
283+ use dodrio::builder::*;
407− bump,
284+
408− "li",
285+ li(bump)
409− [on(bump, "click", move |root, vdom, _event| {
286+ .on("click", move |root, vdom, _event| {
410 C::change_visibility(root, vdom, target_vis);
287 C::change_visibility(root, vdom, target_vis);
411− })],
288+ })
412− [],
413− [Node::element(
414− bump,
289+ .children([a(bump)
415− "a",
416− [],
417− [
418− Attribute {
419− name: "href",
290+ .attr("href", url)
420− value: url,
291+ .attr(
421− },
422− Attribute {
423− name: "class",
292+ "class",
424− value: if self.visibility == target_vis {
293+ if self.visibility == target_vis {
425− "selected"
294+ "selected"
426− } else {
295+ } else {
427− ""
296+ ""
428− },
429 },
297 },
430− ],
298+ )
431− [Node::text(target_vis.label())],
299+ .children([text(target_vis.label())])
432− )],
300+ .finish()])
433− )
301+ .finish()
434 }
302 }
435}
303}
436
304
@@ -439,12 +307,10 @@ impl<C: TodosActions> Render for Todos<C> {
439 where
307 where
440 'a: 'bump,
308 'a: 'bump,
441 {
309 {
442− Node::element(
310+ use dodrio::builder::*;
443− bump,
311+
444− "div",
312+ div(bump)
445− [],
446− [],
447− [self.header(bump), self.todos_list(bump), self.footer(bump)],
313+ .children([self.header(bump), self.todos_list(bump), self.footer(bump)])
448− )
314+ .finish()
449 }
315 }
450}
316}