Skip to content

Instantly share code, notes, and snippets.

@rebo
Last active June 19, 2020 07:01
Show Gist options
  • Save rebo/3030d4cc247ef967062fc51ff2a532eb to your computer and use it in GitHub Desktop.
Save rebo/3030d4cc247ef967062fc51ff2a532eb to your computer and use it in GitHub Desktop.
// 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