-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support boolean attrs, child blocks, and port TodoMVC example.
- Loading branch information
Showing
20 changed files
with
980 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
[package] | ||
authors = ["Nick Fitzgerald <[email protected]>"] | ||
edition = "2018" | ||
name = "dodrio-todomvc" | ||
version = "0.1.0" | ||
|
||
[dependencies] | ||
cfg-if = "0.1.7" | ||
dodrio = "0.1.0" | ||
futures = "0.1.25" | ||
js-sys = "0.3.15" | ||
serde = { features = ["derive"], version = "1.0.89" } | ||
serde_json = "1.0.39" | ||
wasm-bindgen = "0.2.38" | ||
wasm-bindgen-futures = "0.3.15" | ||
typed-html = { path = "../../../typed-html", features = ["dodrio_macro"] } | ||
|
||
# Optional dependencies for logging. | ||
console_error_panic_hook = { optional = true, version = "0.1.6" } | ||
console_log = { optional = true, version = "0.1.2" } | ||
log = { optional = true, version = "0.4.6" } | ||
|
||
[dependencies.web-sys] | ||
version = "0.3.15" | ||
features = [ | ||
"Document", | ||
"Event", | ||
"EventTarget", | ||
"HtmlElement", | ||
"HtmlInputElement", | ||
"KeyboardEvent", | ||
"Location", | ||
"Storage", | ||
"Node", | ||
"Window", | ||
] | ||
|
||
[dev-dependencies] | ||
wasm-bindgen-test = "0.2.38" | ||
|
||
[features] | ||
logging = [ | ||
"console_log", | ||
"console_error_panic_hook", | ||
"log", | ||
] | ||
|
||
[lib] | ||
crate-type = ["cdylib"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# TodoMVC | ||
|
||
`dodrio` implementation of the popular [TodoMVC](http://todomvc.com/) app. It | ||
correctly and completely fulfills [the | ||
specification](https://github.com/tastejs/todomvc/blob/master/app-spec.md) to | ||
the best of my knowledge. | ||
|
||
## Source | ||
|
||
There are a number of modules in this `dodrio` implementation of TodoMVC. The | ||
most important are: | ||
|
||
* `src/lib.rs`: The entry point to the application. | ||
* `src/todos.rs`: Definition of `Todos` model and its rendering. | ||
* `src/todo.rs`: Definition of `Todo` model and its rendering. | ||
* `src/controller.rs`: The controller handles UI interactions and translates | ||
them into updates on the model. Finally, it triggers re-rendering after those | ||
updates. | ||
* `src/router.rs`: A simple URL hash-based router. | ||
|
||
## Build | ||
|
||
``` | ||
wasm-pack build --target no-modules | ||
``` | ||
|
||
## Serve | ||
|
||
Use any HTTP server, for example: | ||
|
||
``` | ||
python -m SimpleHTTPServer | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
<title>dodrio • TodoMVC</title> | ||
<link rel="stylesheet" href="https://unpkg.com/todomvc-common@^1.0.0/base.css"> | ||
<link rel="stylesheet" href="https://unpkg.com/todomvc-app-css@^2.0.0/index.css"> | ||
</head> | ||
<body> | ||
<section class="todoapp"> | ||
</section> | ||
<footer class="info"> | ||
<p>Double-click to edit a todo</p> | ||
</footer> | ||
<script src="pkg/dodrio_todomvc.js"></script> | ||
<script> | ||
wasm_bindgen("pkg/dodrio_todomvc_bg.wasm"); | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
//! The controller handles UI events, translates them into updates on the model, | ||
//! and schedules re-renders. | ||
use crate::todo::{Todo, TodoActions}; | ||
use crate::todos::{Todos, TodosActions}; | ||
use crate::visibility::Visibility; | ||
use dodrio::{RootRender, VdomWeak}; | ||
use serde::{Deserialize, Serialize}; | ||
use std::ops::{Deref, DerefMut}; | ||
|
||
/// The controller for the TodoMVC app. | ||
/// | ||
/// This `Controller` struct is never actually instantiated. It is only used for | ||
/// its `*Actions` trait implementations, none of which take a `self` parameter. | ||
/// | ||
/// One could imagine alternative controller implementations with `*Actions` | ||
/// trait implementations for (e.g.) testing that will assert various expected | ||
/// action methods are called after rendering todo items and sending DOM events. | ||
#[derive(Default, Deserialize, Serialize)] | ||
pub struct Controller; | ||
|
||
impl TodosActions for Controller { | ||
fn toggle_all(root: &mut dyn RootRender, vdom: VdomWeak) { | ||
let mut todos = AutoCommitTodos::new(root, vdom); | ||
let all_complete = todos.todos().iter().all(|t| t.is_complete()); | ||
for t in todos.todos_mut() { | ||
t.set_complete(!all_complete); | ||
} | ||
} | ||
|
||
fn update_draft(root: &mut dyn RootRender, vdom: VdomWeak, draft: String) { | ||
let mut todos = AutoCommitTodos::new(root, vdom); | ||
todos.set_draft(draft); | ||
} | ||
|
||
fn finish_draft(root: &mut dyn RootRender, vdom: VdomWeak) { | ||
let mut todos = AutoCommitTodos::new(root, vdom); | ||
let title = todos.take_draft(); | ||
let title = title.trim(); | ||
if !title.is_empty() { | ||
let id = todos.todos().len(); | ||
let new = Todo::new(id, title); | ||
todos.add_todo(new); | ||
} | ||
} | ||
|
||
fn change_visibility(root: &mut dyn RootRender, vdom: VdomWeak, vis: Visibility) { | ||
let mut todos = AutoCommitTodos::new(root, vdom); | ||
todos.set_visibility(vis); | ||
} | ||
|
||
fn delete_completed(root: &mut dyn RootRender, vdom: VdomWeak) { | ||
let mut todos = AutoCommitTodos::new(root, vdom); | ||
todos.delete_completed(); | ||
} | ||
} | ||
|
||
impl TodoActions for Controller { | ||
fn toggle_completed(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) { | ||
let mut todos = AutoCommitTodos::new(root, vdom); | ||
let t = &mut todos.todos_mut()[id]; | ||
let completed = t.is_complete(); | ||
t.set_complete(!completed); | ||
} | ||
|
||
fn delete(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) { | ||
let mut todos = AutoCommitTodos::new(root, vdom); | ||
todos.delete_todo(id); | ||
} | ||
|
||
fn begin_editing(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) { | ||
let mut todos = AutoCommitTodos::new(root, vdom); | ||
let t = &mut todos.todos_mut()[id]; | ||
let desc = t.title().to_string(); | ||
t.set_edits(Some(desc)); | ||
} | ||
|
||
fn update_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize, edits: String) { | ||
let mut todos = AutoCommitTodos::new(root, vdom); | ||
let t = &mut todos.todos_mut()[id]; | ||
t.set_edits(Some(edits)); | ||
} | ||
|
||
fn finish_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) { | ||
let mut todos = AutoCommitTodos::new(root, vdom); | ||
let t = &mut todos.todos_mut()[id]; | ||
if let Some(edits) = t.take_edits() { | ||
let edits = edits.trim(); | ||
if edits.is_empty() { | ||
todos.delete_todo(id); | ||
} else { | ||
t.set_title(edits); | ||
} | ||
} | ||
} | ||
|
||
fn cancel_edits(root: &mut dyn RootRender, vdom: VdomWeak, id: usize) { | ||
let mut todos = AutoCommitTodos::new(root, vdom); | ||
let t = &mut todos.todos_mut()[id]; | ||
let _ = t.take_edits(); | ||
} | ||
} | ||
|
||
/// An RAII type that dereferences to a `Todos` and once it is dropped, saves | ||
/// the (presumably just modified) todos to local storage, and schedules a new | ||
/// `dodrio` render. | ||
pub struct AutoCommitTodos<'a> { | ||
todos: &'a mut Todos, | ||
vdom: VdomWeak, | ||
} | ||
|
||
impl AutoCommitTodos<'_> { | ||
/// Construct a new `AutoCommitTodos` from the root rendering component and | ||
/// `vdom` handle. | ||
pub fn new(root: &mut dyn RootRender, vdom: VdomWeak) -> AutoCommitTodos { | ||
let todos = root.unwrap_mut::<Todos>(); | ||
AutoCommitTodos { todos, vdom } | ||
} | ||
} | ||
|
||
impl Drop for AutoCommitTodos<'_> { | ||
fn drop(&mut self) { | ||
self.todos.save_to_local_storage(); | ||
self.vdom.schedule_render(); | ||
} | ||
} | ||
|
||
impl Deref for AutoCommitTodos<'_> { | ||
type Target = Todos; | ||
|
||
fn deref(&self) -> &Todos { | ||
&self.todos | ||
} | ||
} | ||
|
||
impl DerefMut for AutoCommitTodos<'_> { | ||
fn deref_mut(&mut self) -> &mut Todos { | ||
&mut self.todos | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
//! Constants for `KeyboardEvent::key_code`.` | ||
/// The key code for the enter key. | ||
pub const ENTER: u32 = 13; | ||
|
||
/// The key code for the escape key. | ||
pub const ESCAPE: u32 = 27; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
//! TodoMVC implemented in `dodrio`! | ||
#![recursion_limit = "1024"] | ||
#![deny(missing_docs)] | ||
|
||
pub mod controller; | ||
pub mod keys; | ||
pub mod router; | ||
pub mod todo; | ||
pub mod todos; | ||
pub mod utils; | ||
pub mod visibility; | ||
|
||
use crate::controller::Controller; | ||
use crate::todos::Todos; | ||
use dodrio::Vdom; | ||
use wasm_bindgen::prelude::*; | ||
|
||
/// Run the TodoMVC app! | ||
/// | ||
/// Since this is marked `#[wasm_bindgen(start)]` it is automatically invoked | ||
/// once the wasm module instantiated on the Web page. | ||
#[wasm_bindgen(start)] | ||
pub fn run() -> Result<(), JsValue> { | ||
// Set up the logging for debugging if/when things go wrong. | ||
init_logging(); | ||
|
||
// Grab the todo app container. | ||
let document = utils::document(); | ||
let container = document | ||
.query_selector(".todoapp")? | ||
.ok_or_else(|| js_sys::Error::new("could not find `.todoapp` container"))?; | ||
|
||
// Create a new `Todos` render component. | ||
let todos = Todos::<Controller>::new(); | ||
|
||
// Create a virtual DOM and mount it and the `Todos` render component. | ||
let vdom = Vdom::new(&container, todos); | ||
|
||
// Start the URL router. | ||
router::start(vdom.weak()); | ||
|
||
// Run the virtual DOM forever and don't unmount it. | ||
vdom.forget(); | ||
|
||
Ok(()) | ||
} | ||
|
||
cfg_if::cfg_if! { | ||
if #[cfg(feature = "logging")] { | ||
fn init_logging() { | ||
console_error_panic_hook::set_once(); | ||
console_log::init_with_level(log::Level::Trace) | ||
.expect_throw("should initialize logging OK"); | ||
} | ||
} else { | ||
fn init_logging() { | ||
// Do nothing. | ||
} | ||
} | ||
} |
Oops, something went wrong.