Last active
June 19, 2020 07:01
-
-
Save rebo/3030d4cc247ef967062fc51ff2a532eb to your computer and use it in GitHub Desktop.
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
// An example To-do seed application, includes filtering of todos | |
// Each UI component is naturally scoped to specific state changes. | |
// This makes each part understandable | |
// The UI elements are themselves `#[computed]` properties of the | |
// underyling state and only update if the underlying state changes. | |
#[derive(Clone)] | |
struct Todo{ | |
idx: usize, | |
description: String, | |
complete: bool, | |
} | |
// Atom of state, a vector containing Todo structs | |
#[state] | |
fn todos()-> Vec<Todo>{ | |
vec![] | |
} | |
#[derive(Clone, PartialEq)] | |
enum FilterStatus { | |
ShowAll, | |
Complete, | |
Incomplete, | |
} | |
//The state of the chosen filter method, defaults to show all | |
#[state] | |
fn filter_state()-> FilterStatus { | |
FilterStatus::ShowAll | |
} | |
// Automatically re-computes filtered todos when (and only when todos or the chosen filter are are updated) | |
#[computed] | |
fn filtered_todos() -> Vec<Todo> { | |
// These two lines ensure that this function is only rerun when todos or filter_state change. | |
let todos = link_state(todos()); | |
let filter = link_state(filter_state()); | |
match filter { | |
FilterStatus::ShowAll => todos, | |
FilterStatus::Complete => todos.iter().filter(|t| t.complete).cloned().collect::<Vec<_>>(), | |
FilterStatus::Incomplete => todos.iter().filter(|t| !t.complete).cloned().collect::<Vec<_>>(), | |
} | |
} | |
// Automatically generates a show all button snippet dependent upon the filter state changing | |
// The interesting thing is this button is a naturally discrete snippet of UI which is now directly | |
// scoped to a change in filter_state. | |
// Notice it is not dependent upon the todo list itself, nor should it be, this just colours the button | |
// green if the filter state is `ShowAll`. | |
#[computed] | |
fn show_all_button() -> Node<Msg> { | |
let filter = link_state(filter_state()); | |
fancy_button![ | |
if filter == FilterStatus::ShowAll{ | |
s().bg_color(rgb(0,255,0)).font_weight_v900() | |
} else { | |
s().bg_color(rgb(180,120,120)) | |
}, | |
"Show All", | |
mouse_ev(Ev::Click, |_| | |
filter_state().update(|s| *s = FilterStatus::ShowAll) | |
)] | |
} | |
// Automatically generates a show complete button snippet dependent upon the filter state changing | |
#[computed] | |
fn show_complete_button() -> Node<Msg> { | |
let filter = link_state(filter_state()); | |
fancy_button![ | |
if filter == FilterStatus::Complete { | |
s().bg_color(rgb(0,255,0)).font_weight_v900() | |
} else { | |
s().bg_color(rgb(180,120,120)) | |
}, | |
"Show Complete", | |
mouse_ev(Ev::Click, |_| | |
filter_state().update(|s| *s = FilterStatus::Complete ) | |
)] | |
} | |
// Automatically generates a show incomplete button snippet dependent upon the filter state changing | |
#[computed] | |
fn show_incomplete_button() -> Node<Msg> { | |
let filter = link_state(filter_state()); | |
fancy_button![ | |
if filter == FilterStatus::Incomplete{ | |
s().bg_color(rgb(0,255,0)).font_weight_v900() | |
} else { | |
s().bg_color(rgb(180,120,120)) | |
}, | |
"Show Incomplete", | |
mouse_ev(Ev::Click, |_| | |
filter_state().update(|s|*s = FilterStatus::Incomplete ) | |
)] | |
} | |
// Input state for the button element, defaults to empty string | |
#[state] | |
fn todo_input_state() -> String { | |
"".to_string() | |
} | |
// A derived UI snippet to manage creation of a new to do. | |
// Due to the self contained aspect of this UI it is clear | |
// it depends on the todo_input_state and it also sets up an event handler | |
// to update this state | |
// | |
// All we need to know about this aspect of the app is in this snippet and the | |
// above state atom. | |
#[computed] | |
fn add_todo() -> Vec<Node<Msg>>{ | |
let todo_input = link_state(todo_input_state()); | |
vec![ | |
input![ | |
attrs!(At::Value => todo_input), | |
s().b_style_solid().b_width(px(1)).b_color("#333333"), | |
input_ev(Ev::Input, | |
|s| todo_input_state().update(|t| *t = s ) | |
) | |
], | |
fancy_button![ | |
s().bg_color("#99CCFF"), | |
"Add Todo", | |
mouse_ev(Ev::Click, |_| | |
// when add todo is pressed add a update the todos state | |
// and clare the input state | |
todos().update(|t|{ | |
t.push( | |
Todo { | |
idx: t.len(), | |
description: todo_input, | |
complete: false, | |
} | |
); | |
todo_input_state().update(|t| *t = "".to_string()) | |
}) | |
) | |
] | |
] | |
} | |
// Auto generates a filtered todo list view if the filtered todo list state changes. | |
#[computed] | |
fn filtered_todos_list() -> Vec<Node<Msg>>{ | |
let filtered_todos = link_state(filtered_todos()); | |
filtered_todos.iter().map(|todo| { | |
let todo_idx = todo.idx; | |
li![ | |
s().display_flex().justify_content_flex_end().align_content_center(), | |
center![todo.description.clone()], | |
fancy_button![ | |
if todo.complete{ | |
s().bg_color(rgb(120,255,120)) | |
} else { | |
s().bg_color(rgb(120,120,120)) | |
} | |
,"X", | |
mouse_ev(Ev::Click, move |_| { | |
todos().update(|ts| | |
for t in ts.iter_mut(){ | |
if t.idx == todo_idx {t.complete = !t.complete} | |
} | |
) | |
}) | |
] | |
] | |
} | |
).collect::<Vec<_>>() | |
} | |
// The main view, as you can it is a column of title, input , controls and list | |
// all the main UI components are naturally cached via #[computed] | |
// no additional configuration needed, they only re-initialise when the underlying todo list | |
// or filter options change. | |
// views can be naturally smaller and composed of functions that build the UI | |
// based on state changes in the dependency tree. | |
pub fn view(_model: &Model) -> Node<Msg> { | |
Column![ | |
Item![ | |
align = ColumnAlign::TopCenter, | |
h1!["Todos"] | |
], | |
Item![ | |
align = ColumnAlign::TopCenter, | |
flex = Flex::None, | |
add_todo() | |
], | |
Item![ | |
align = ColumnAlign::TopCenter, | |
flex = Flex::None, | |
show_all_button(), | |
show_complete_button(), | |
show_incomplete_button() | |
], | |
Item![ | |
align = ColumnAlign::TopCenter, | |
filtered_todos_list(), | |
] | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment