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()
    }
}