observable_react/
lib.rs

1//! Export and bind Observables to react applications using WASM
2//! ## Example
3//! ```
4//!
5//! ```
6
7pub mod collections;
8pub mod impls;
9pub mod react;
10pub mod traits;
11
12use crate::traits::JsObserve;
13use wasm_bindgen::{prelude::*, JsValue};
14
15/// # Wrapper around Observable<T> for usage in javascript/typescript
16/// ```
17///
18/// # use std::rc::Rc;
19/// # use observable_react::JsObservable;
20/// # use observable_rs::Observable;
21/// # use serde::Serialize;
22/// # use wasm_bindgen::prelude::*;
23/// # use std::cell::RefCell;
24///
25/// #[derive(Clone, Default)]
26/// #[wasm_bindgen]
27/// pub struct CatState { cats: usize };
28/// impl CatState {
29///   fn new (cats: usize) -> Self {
30///     CatState{cats}
31///   }
32/// }
33/// #[wasm_bindgen]
34/// impl CatState {
35///   #[wasm_bindgen(getter)]
36///   pub fn cats(&self) -> usize {
37///     self.cats
38///   }
39/// }
40///
41/// #[derive(Default, Clone, Serialize)]
42/// pub struct Bar(pub Vec<usize>);
43///
44/// impl Into<JsValue> for Bar {
45///     fn into(self) -> JsValue {
46///         JsValue::from_serde(&self).unwrap()
47///     }
48/// }
49///
50/// let obs = Observable::new(CatState::new(1));
51/// let obsJs: JsObservable = obs.clone().into();
52/// let lastRustCats: Rc<RefCell<Option<usize>>> = Rc::new(RefCell::new(None));
53/// let lrc = lastRustCats.clone();
54/// obs.subscribe(Box::new(move |v|{
55///   *(lrc.borrow_mut()) = Some(v.cats);
56///   println!("Rust Cats: {}", v.cats);
57/// }));
58/// // In JS:
59/// // catStateJs.subscribe(() => console.log(`JS Cats ${catstate.cats}`));
60///
61/// // We are not presently firing the listener for initial state
62/// assert_eq!(*(lastRustCats.borrow()), None);
63/// obs.set(CatState::new(7));
64/// assert_eq!(*(lastRustCats.borrow()), Some(7));
65///
66/// // Both Rust Cats and JS Cats logs are printed
67///
68/// let barObsJs: JsObservable = Observable::new(Bar::default()).into();
69/// let strObsJs: JsObservable = Observable::new(String::from("Meow")).into();
70/// let intObsJs: JsObservable = Observable::new(123).into();
71/// let fltObsJs: JsObservable = Observable::new(123.0).into();
72/// ```
73#[wasm_bindgen]
74pub struct JsObservable {
75    obs: Box<dyn JsObserve>,
76}
77
78impl JsObservable {
79    pub fn new(obs: Box<dyn JsObserve>) -> Self {
80        JsObservable { obs }
81    }
82}
83
84#[wasm_bindgen]
85impl JsObservable {
86    pub fn get(&self) -> JsValue {
87        self.obs.get_js()
88    }
89    pub fn map(&self, cb: js_sys::Function) -> JsValue {
90        self.obs.map_js(cb)
91    }
92    pub fn subscribe(
93        &mut self,
94        cb: js_sys::Function,
95        // TODO: ChangeContext contract from TS?
96    ) -> js_sys::Function {
97        let handle = self.obs.subscribe(Box::new(move |v: JsValue| {
98            cb.call1(&JsValue::UNDEFINED, &v).unwrap();
99        }));
100
101        // Make a copy that the closure can hold on to
102        let obs = dyn_clone::clone_box(&*self.obs);
103
104        let unsub = Closure::once_into_js(Box::new(move || {
105            obs.unsubscribe(handle);
106        }) as Box<dyn FnOnce()>);
107
108        unsub.into()
109    }
110
111    pub fn destroy(&self) {
112        // NOOP. Call the free() method instead
113    }
114
115    #[wasm_bindgen(getter)]
116    pub fn value(&self) -> JsValue {
117        self.obs.get_js()
118    }
119
120    pub fn load(&self) -> js_sys::Promise {
121        // TODO implement loaders in observable_rs
122        js_sys::Promise::resolve(&JsValue::null())
123    }
124}
125
126impl<O> From<O> for JsObservable
127where
128    O: JsObserve + 'static + Sized,
129{
130    fn from(obs: O) -> Self {
131        JsObservable::new(Box::new(obs))
132    }
133}