2022-10-02 18:16:10 -04:00
|
|
|
use js_sys::Function;
|
2022-10-02 18:21:24 -04:00
|
|
|
use wasm_bindgen::{convert::IntoWasmAbi, prelude::*, JsCast, JsValue};
|
2022-10-02 18:16:10 -04:00
|
|
|
use web_sys::window;
|
|
|
|
|
|
|
|
type Result<T> = std::result::Result<T, JsValue>;
|
|
|
|
|
2022-10-02 18:21:24 -04:00
|
|
|
pub trait CustomElementImpl: IntoWasmAbi {
|
2022-10-02 18:16:10 -04:00
|
|
|
fn class_name() -> &'static str;
|
|
|
|
fn element_name() -> &'static str;
|
2022-10-02 18:21:24 -04:00
|
|
|
|
|
|
|
fn construct() -> Self;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct WebComponentHandle<T: CustomElementImpl + 'static> {
|
|
|
|
pub impl_handle: Closure<dyn FnMut() -> T>,
|
|
|
|
pub element_constructor: Function,
|
2022-10-02 18:16:10 -04:00
|
|
|
}
|
|
|
|
|
2022-10-02 18:21:24 -04:00
|
|
|
pub fn define_web_component<T>() -> Result<WebComponentHandle<T>>
|
|
|
|
where
|
|
|
|
T: CustomElementImpl + 'static,
|
|
|
|
{
|
2022-10-02 18:16:10 -04:00
|
|
|
let body = format!(
|
|
|
|
"class {name} extends HTMLElement {{
|
|
|
|
constructor() {{
|
|
|
|
super();
|
2022-10-02 18:21:24 -04:00
|
|
|
//this.impl = impl();
|
2022-10-02 18:16:10 -04:00
|
|
|
}}
|
|
|
|
}}
|
|
|
|
customElements.define(\"{element_name}\", {name});
|
|
|
|
var element = customElements.get(\"{element_name}\");
|
|
|
|
return element;",
|
|
|
|
name = T::class_name(),
|
|
|
|
element_name = T::element_name(),
|
|
|
|
);
|
2022-10-02 18:21:24 -04:00
|
|
|
let fun = Function::new_with_args("impl", &body);
|
|
|
|
let f: Box<dyn FnMut() -> T> = Box::new(|| T::construct());
|
|
|
|
// TODO(jwall): Check the lifetimes on this guy.
|
|
|
|
let constructor_handle = Closure::wrap(f);
|
|
|
|
let element = fun
|
|
|
|
.call1(
|
|
|
|
&window().unwrap(),
|
|
|
|
constructor_handle.as_ref().unchecked_ref::<Function>(),
|
|
|
|
)?
|
|
|
|
.dyn_into()?;
|
|
|
|
Ok(WebComponentHandle {
|
|
|
|
element_constructor: element,
|
|
|
|
impl_handle: constructor_handle,
|
|
|
|
})
|
2022-10-02 18:16:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2022-10-02 18:21:24 -04:00
|
|
|
use wasm_bindgen::JsCast;
|
2022-10-02 18:16:10 -04:00
|
|
|
use wasm_bindgen_test::wasm_bindgen_test;
|
|
|
|
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
|
|
|
|
2022-10-02 18:21:24 -04:00
|
|
|
#[wasm_bindgen]
|
2022-10-02 18:16:10 -04:00
|
|
|
pub struct MyElementImpl();
|
|
|
|
|
|
|
|
impl CustomElementImpl for MyElementImpl {
|
|
|
|
fn class_name() -> &'static str {
|
|
|
|
"MyElement"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn element_name() -> &'static str {
|
|
|
|
"my-element"
|
|
|
|
}
|
2022-10-02 18:21:24 -04:00
|
|
|
|
|
|
|
fn construct() -> Self {
|
|
|
|
Self()
|
|
|
|
}
|
2022-10-02 18:16:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[wasm_bindgen]
|
|
|
|
extern "C" {
|
|
|
|
#[wasm_bindgen(js_namespace = console, js_name = log)]
|
|
|
|
pub fn log(message: String);
|
|
|
|
#[wasm_bindgen(js_namespace = console, js_name = log)]
|
|
|
|
pub fn log_with_val(message: String, val: &JsValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[wasm_bindgen_test]
|
|
|
|
fn test_component_definition() {
|
|
|
|
let obj = define_web_component::<MyElementImpl>().expect("Failed to define web component");
|
2022-10-02 18:21:24 -04:00
|
|
|
let fun = obj.element_constructor.dyn_ref::<Function>().unwrap();
|
2022-10-02 18:16:10 -04:00
|
|
|
assert_eq!(fun.name(), MyElementImpl::class_name());
|
|
|
|
|
|
|
|
let element = window()
|
|
|
|
.unwrap()
|
|
|
|
.document()
|
|
|
|
.unwrap()
|
|
|
|
.create_element(MyElementImpl::element_name())
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
element.tag_name().to_uppercase(),
|
|
|
|
MyElementImpl::element_name().to_uppercase()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|