1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
use crate::util::document; use std::{cell::RefCell, marker::PhantomData, rc::Rc}; use wasm_bindgen::JsCast; /// Attaches given `ElRef` to the DOM element. /// /// See `ElRef` for more info. pub fn el_ref<E: Clone>(reference: &ElRef<E>) -> ElRef<E> { reference.clone() } // ------ ElRef ------ /// DOM element reference. /// You want to use it instead of DOM selectors to get raw DOM elements. /// /// _Note_: Cloning is cheap, it uses only phantom data and `Rc` under the hood. /// /// # Example /// /// ```rust,no_run /// #[derive(Default)] /// struct Model { /// canvas: ElRef<web_sys::HtmlCanvasElement>, /// } /// /// fn view(model: &Model) -> impl View<Msg> { /// canvas![ /// el_ref(&model.canvas), /// attrs![ /// At::Width => px(200), /// At::Height => px(100), /// ], /// ] /// } /// /// fn after_mount(_: Url, orders: &mut impl Orders<Msg>) -> AfterMount<Model> { /// orders.after_next_render(|_| Msg::Rendered); /// // ... /// /// fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) { /// match msg { /// Msg::Rendered => { /// let canvas = canvas.get().expect("get canvas element"); /// // ... /// orders.after_next_render(|_| Msg::Rendered).skip(); /// } /// // ... /// ``` #[derive(Clone)] pub struct ElRef<E> { pub shared_node_ws: SharedNodeWs, // We need to use `phantom` to remember required element type `E`, // so we can use it automatically in `get` method for casting. phantom: PhantomData<E>, } impl<E: Clone + JsCast> ElRef<E> { pub fn new() -> Self { Self { // We need to use interior mutability // to modify `Model` from `view` during VDOM patching. shared_node_ws: SharedNodeWs::new(), phantom: PhantomData, } } /// Get referenced DOM element. /// /// It returns `Some(element)` when: /// - An associated DOM element has been already attached during render. /// - The DOM element is still a part of the current DOM. /// - The DOM element has the same type like `ElRef`. pub fn get(&self) -> Option<E> { // Has `node_ws` already been assigned by VDOM? let node_ws = match self.shared_node_ws.clone_inner() { Some(node_ws) => node_ws, None => return None, }; // Is `node_ws` in the current DOM? if !document().contains(Some(&node_ws)) { return None; } // Try to cast to the chosen element type. node_ws.dyn_into::<E>().ok() } /// Map `ElRef` type. /// - It just changes type saved in the phantom - it's cheap. /// /// - It's useful when you have, for instance, `ElRef<HtmlInputElement>` /// and want to focus the referenced input. `HtmlInputElement` doesn't have method `focus`, /// but parent interface `HtmlElement` has. /// /// # Example /// /// ```rust,no_run /// let input: ElRef<HtmlInputElement> = model.refs.my_input.clone(); /// orders.after_next_render(move |_| { /// input /// .map_type::<HtmlElement>() /// .get() /// .expect("get `my_input`") /// .focus() /// .expect("focus 'my_input'"); /// Msg::NoOp /// }); /// pub fn map_type<T>(&self) -> ElRef<T> { ElRef { shared_node_ws: self.shared_node_ws.clone(), phantom: PhantomData, } } } impl<E> Default for ElRef<E> { fn default() -> Self { Self { shared_node_ws: SharedNodeWs::new(), phantom: PhantomData, } } } // ------ SharedNodeWs ------ #[derive(Debug, Default, Clone)] pub struct SharedNodeWs(Rc<RefCell<Option<web_sys::Node>>>); impl SharedNodeWs { pub fn new() -> Self { Self(Rc::new(RefCell::new(None))) } pub fn set(&mut self, node_ws: web_sys::Node) { self.0.replace(Some(node_ws)); } pub fn clone_inner(&self) -> Option<web_sys::Node> { self.0.borrow().clone() } }