rust_fel/
component.rs

1use crate::element::Element;
2
3/// ```Components``` are the basic building blocks of the UI. Each ```Component```
4/// chooses how to display itself using received props and self-managed state.
5/// Inspired by [Yew](https://github.com/yewstack/yew)'s [Component](https://docs.rs/yew/0.17.3/yew/html/trait.Component.html)
6/// # Examples
7/// A rust_fel [struct](https://doc.rust-lang.org/std/keyword.struct.html) component implements [rust_fel::Component](../rust_fel/trait.Component.html)
8///```ignore
9///use crate::action::Action;
10///use crate::handle;
11///use crate::main_child::{ChildProps, MainChild};
12///use std::cell::RefCell;
13///use std::rc::Rc;
14///use wasm_bindgen::JsCast;
15///
16///#[derive(Debug, Default, Clone)]
17///pub struct MainState {
18///    count: i32,
19///}
20///
21///pub enum Actions {
22///    Counter(Action),
23///}
24///
25///#[derive(Debug, Default, Clone)]
26///pub struct Main {
27///    child: handle::Handle<MainChild>,
28///    id: String,
29///    state: MainState,
30///    props: String,
31///}
32///
33///impl Main {
34///    pub fn create() -> handle::Handle<Self> {
35///        let main = Main {
36///            id: "main".to_owned(),
37///            state: MainState {
38///                count: 0,
39///            },
40///            child: MainChild::create(),
41///            ..Default::default()
42///        };
43///        handle::Handle(Rc::new(RefCell::new(main)))
44///    }
45///}
46///
47///impl rust_fel::Component for handle::Handle<Main> {
48///    type Properties = String;
49///    type Message = Actions;
50///    type State = MainState;
51///
52///    fn add_props(&mut self, props: Self::Properties) {
53///        self.0.borrow_mut().props = props;
54///    }
55///
56///    fn reduce_state(&mut self, message: Actions) {
57///        match message {
58///            Actions::Counter(Action::Increment) => self.0.borrow_mut().state.count += 100,
59///            Actions::Counter(Action::Decrement) => self.0.borrow_mut().state.count -= 100,
60///        }
61///
62///        rust_fel::re_render(self.render(), Some(self.0.borrow().id.clone()));
63///    }
64///
65///    fn render(&self) -> rust_fel::Element {
66///        let mut clone_for_props_closure = self.clone();
67///        let mut clone_for_inc = self.clone();
68///        let mut borrow = self.0.borrow_mut();
69///        let state = borrow.state.clone();
70///        let props_closure = Rc::new(RefCell::new(move || {
71///            clone_for_props_closure.reduce_state(Actions::Counter(Action::Decrement))
72///        }));
73///
74///        let child_props = ChildProps {
75///            counter_props: state.count.to_string(),
76///            closure: Some(props_closure),
77///        };
78///
79///        borrow.child.add_props(child_props);
80///
81///        let main_text = rust_fel::html(format!(
82///            "<span | data-cy=main-text| >Main {}</span>",
83///            state.count.to_string()
84///        ));
85///
86///        let inc_button = rust_fel::Element::new(
87///            "button".to_owned(),
88///            rust_fel::Props {
89///                text: Some("Increment".to_owned()),
90///                on_click: Some(Box::new(move || {
91///                    clone_for_inc.reduce_state(Actions::Counter(Action::Increment))
92///                })),
93///                data_cy: Some("increment-main".to_owned()),
94///                children: Some(vec![inc_button_text]),
95///                ..Default::default()
96///            },
97///        );
98///
99///        let main_el = rust_fel::Element::new(
100///            "div".to_owned(),
101///            rust_fel::Props {
102///                class_name: Some("main-el".to_owned()),
103///                children: Some(vec![main_text, inc_button, input_wrapper]),
104///                ..Default::default()
105///            },
106///        );
107///
108///        let child_wrapper = rust_fel::Element::new(
109///            "div".to_owned(),
110///            rust_fel::Props {
111///                class_name: Some("child-wrapper".to_owned()),
112///                children: Some(vec![borrow.child.render()]),
113///                ..Default::default()
114///            },
115///        );
116///
117///        rust_fel::Element::new(
118///            "div".to_owned(),
119///            rust_fel::Props {
120///                id: Some(borrow.id.clone()),
121///                class_name: Some("main".to_owned()),
122///                children: Some(vec![main_el, child_wrapper]),
123///                ..Default::default()
124///            },
125///        )
126///    }
127///}
128///
129///```
130pub trait Component: Sized + 'static {
131    /// Messages are used to make ```Components``` dynamic and interactive.
132    type Message: 'static;
133
134    /// ```Properties``` are the inputs to a ```Component``` and should not be mutated.
135    type Properties: 'static;
136
137    /// A ```Component's``` internal state.
138    type State: 'static;
139
140    /// Construct your html view here.
141    fn render(&self) -> Element;
142
143    /// How a ```Component manages``` internal state.
144    fn reduce_state(&mut self, message: Self::Message);
145
146    /// Invoked by a ```Component's``` parent in order to pass properties.
147    fn add_props(&mut self, props: Self::Properties);
148}