use crate::{
dom::Dispatch,
html,
mt_dom::{
Callback,
NodeIdx,
},
Attribute,
};
use std::{
collections::HashMap,
ops::Deref,
sync::Mutex,
};
use wasm_bindgen::{
closure::Closure,
JsCast,
};
use web_sys::{
self,
Element,
EventTarget,
HtmlInputElement,
HtmlTextAreaElement,
Node,
Text,
};
use lazy_static::lazy_static;
lazy_static! {
static ref DATA_SAURON_VDOM_ID_VALUE: Mutex<u32> = Mutex::new(0);
}
fn create_unique_identifier() -> u32 {
let mut elem_unique_id = DATA_SAURON_VDOM_ID_VALUE
.lock()
.expect("Unable to obtain lock");
*elem_unique_id += 1;
*elem_unique_id
}
pub(crate) const DATA_SAURON_VDOM_ID: &str = "data-sauron-vdom-id";
pub type ActiveClosure =
HashMap<u32, Vec<(&'static str, Closure<dyn FnMut(web_sys::Event)>)>>;
#[derive(Debug)]
pub struct CreatedNode<T> {
pub node: T,
pub(crate) closures: ActiveClosure,
}
impl<T> CreatedNode<T> {
pub fn without_closures<N: Into<T>>(node: N) -> Self {
CreatedNode {
node: node.into(),
closures: HashMap::with_capacity(0),
}
}
pub fn create_text_node(txt: &str) -> Text {
crate::document().create_text_node(txt)
}
pub fn create_dom_node<DSP, MSG>(
program: &DSP,
vnode: &crate::Node<MSG>,
node_idx: &mut Option<NodeIdx>,
) -> CreatedNode<Node>
where
MSG: 'static,
DSP: Clone + Dispatch<MSG> + 'static,
{
Self::create_dom_node_opt(Some(program), vnode, node_idx)
}
pub fn create_dom_node_opt<DSP, MSG>(
program: Option<&DSP>,
vnode: &crate::Node<MSG>,
node_idx: &mut Option<NodeIdx>,
) -> CreatedNode<Node>
where
MSG: 'static,
DSP: Clone + Dispatch<MSG> + 'static,
{
match vnode {
crate::Node::Text(txt) => {
CreatedNode::without_closures(Self::create_text_node(txt))
}
crate::Node::Element(element_node) => {
let created_element: CreatedNode<Node> =
Self::create_element_node(program, element_node, node_idx)
.into();
created_element
}
}
}
pub fn set_element_attributes<DSP, MSG>(
program: Option<&DSP>,
closures: &mut ActiveClosure,
element: &Element,
attrs: &[&Attribute<MSG>],
) where
MSG: 'static,
DSP: Clone + Dispatch<MSG> + 'static,
{
let attrs = mt_dom::merge_attributes_of_same_name(attrs);
for att in attrs {
Self::set_element_attribute(program, closures, element, &att);
}
}
pub fn set_element_attribute<DSP, MSG>(
program: Option<&DSP>,
closures: &mut ActiveClosure,
element: &Element,
attr: &Attribute<MSG>,
) where
MSG: 'static,
DSP: Clone + Dispatch<MSG> + 'static,
{
let (callbacks, plain_values, func_values) =
html::attributes::partition_callbacks_from_plain_and_func_calls(
attr,
);
if let Some(merged_plain_values) =
html::attributes::merge_plain_attributes_values(&plain_values)
{
if let Some(ref namespace) = attr.namespace() {
element
.set_attribute_ns(
Some(namespace),
attr.name(),
&merged_plain_values,
)
.expect("Set element attribute_ns in create element");
} else {
match *attr.name() {
"value" => {
if let Some(input) =
element.dyn_ref::<HtmlInputElement>()
{
input.set_value(&merged_plain_values);
} else if let Some(textarea) =
element.dyn_ref::<HtmlTextAreaElement>()
{
textarea.set_value(&merged_plain_values);
}
}
"checked" => {
if let Some(input) =
element.dyn_ref::<HtmlInputElement>()
{
let checked = plain_values
.first()
.map(|av| {
av.get_simple()
.expect("must be a simple value")
})
.unwrap()
.to_string();
if !checked.is_empty() {
input.set_checked(true);
}
}
}
_ => {
element
.set_attribute(attr.name(), &merged_plain_values)
.expect("Set element attribute in create element");
}
}
}
} else {
element
.remove_attribute(attr.name())
.expect("must remove attribute");
}
if let Some(merged_func_values) =
html::attributes::merge_plain_attributes_values(&func_values)
{
match *attr.name() {
"inner_html" => element.set_inner_html(&merged_func_values),
_ => (),
}
}
for callback in callbacks {
let unique_id = create_unique_identifier();
element
.set_attribute(DATA_SAURON_VDOM_ID, &unique_id.to_string())
.expect("Could not set attribute on element");
closures.insert(unique_id, vec![]);
if let Some(program) = program {
let event_str = attr.name();
let current_elm: &EventTarget =
element.dyn_ref().expect("unable to cast to event targe");
let closure_wrap: Closure<dyn FnMut(web_sys::Event)> =
create_closure_wrap(program, &callback);
current_elm
.add_event_listener_with_callback(
event_str,
closure_wrap.as_ref().unchecked_ref(),
)
.expect("Unable to attached event listener");
closures
.get_mut(&unique_id)
.expect("Unable to get closure")
.push((event_str, closure_wrap));
}
}
}
pub fn create_element_node<DSP, MSG>(
program: Option<&DSP>,
velem: &crate::Element<MSG>,
node_idx: &mut Option<NodeIdx>,
) -> CreatedNode<Element>
where
MSG: 'static,
DSP: Clone + Dispatch<MSG> + 'static,
{
let document = crate::document();
let element = if let Some(ref namespace) = velem.namespace() {
document
.create_element_ns(Some(namespace), &velem.tag())
.expect("Unable to create element")
} else {
document
.create_element(&velem.tag())
.expect("Unable to create element")
};
let mut closures = ActiveClosure::new();
Self::set_element_attributes(
program,
&mut closures,
&element,
&velem.get_attributes().iter().collect::<Vec<_>>(),
);
#[cfg(feature = "with-nodeidx")]
if let Some(node_idx) = node_idx {
Self::set_element_attributes(
program,
&mut closures,
&element,
&[&crate::prelude::attr("node_idx", *node_idx)],
);
}
let mut previous_node_was_text = false;
for child in velem.get_children().iter() {
match child {
crate::Node::Text(txt) => {
node_idx.as_mut().map(|idx| *idx += 1);
let current_node: &web_sys::Node = element.as_ref();
if previous_node_was_text {
let separator = document.create_comment("mordor");
current_node
.append_child(separator.as_ref())
.expect("Unable to append child");
}
current_node
.append_child(&Self::create_text_node(&txt))
.expect("Unable to append text node");
previous_node_was_text = true;
}
crate::Node::Element(element_node) => {
node_idx.as_mut().map(|idx| *idx += 1);
previous_node_was_text = false;
let child = Self::create_element_node(
program,
element_node,
node_idx,
);
let child_elem: Element = child.node;
closures.extend(child.closures);
element
.append_child(&child_elem)
.expect("Unable to append element node");
}
}
}
CreatedNode {
node: element,
closures,
}
}
}
pub(crate) fn create_closure_wrap<DSP, MSG>(
program: &DSP,
callback: &Callback<crate::Event, MSG>,
) -> Closure<dyn FnMut(web_sys::Event)>
where
MSG: 'static,
DSP: Clone + Dispatch<MSG> + 'static,
{
let callback_clone = callback.clone();
let program_clone = program.clone();
Closure::wrap(Box::new(move |event: web_sys::Event| {
let msg = callback_clone.emit(event);
program_clone.dispatch(msg);
}))
}
impl From<CreatedNode<Element>> for CreatedNode<Node> {
fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
CreatedNode {
node: other.node.into(),
closures: other.closures,
}
}
}
impl<T> Deref for CreatedNode<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.node
}
}