use crate::{
dom::Dispatch,
mt_dom::{AttValue, Callback},
prelude::AttributeValue,
Attribute, Event,
};
use std::ops::Deref;
use std::{collections::HashMap, fmt::Write, 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(text: &str) -> Text {
crate::document().create_text_node(text)
}
pub fn create_dom_node<DSP, MSG>(
program: &DSP,
vnode: &crate::Node<MSG>,
) -> CreatedNode<Node>
where
MSG: 'static,
DSP: Clone + Dispatch<MSG> + 'static,
{
Self::create_dom_node_opt(Some(program), vnode)
}
pub fn create_dom_node_opt<DSP, MSG>(
program: Option<&DSP>,
vnode: &crate::Node<MSG>,
) -> CreatedNode<Node>
where
MSG: 'static,
DSP: Clone + Dispatch<MSG> + 'static,
{
match vnode {
crate::Node::Text(text_node) => {
CreatedNode::without_closures(Self::create_text_node(text_node))
}
crate::Node::Element(element_node) => {
let created_element: CreatedNode<Node> =
Self::create_element_node(program, element_node).into();
created_element
}
}
}
fn merge_plain_attributes_values(
attr_values: &[&AttributeValue],
) -> Option<String> {
let plain_values: Vec<String> = attr_values
.iter()
.flat_map(|att_value| match att_value {
AttributeValue::Simple(simple) => Some(simple.to_string()),
AttributeValue::Style(styles) => {
let mut style_str = String::new();
styles.iter().for_each(|s| {
write!(style_str, "{};", s).expect("must write")
});
Some(style_str)
}
AttributeValue::FunctionCall(fvalue) => {
Some(fvalue.to_string())
}
AttributeValue::Empty => None,
})
.collect();
if !plain_values.is_empty() {
Some(plain_values.join(" "))
} else {
None
}
}
fn partition_callbacks_from_plain_and_func_calls<MSG>(
attr: &Attribute<MSG>,
) -> (
Vec<&Callback<Event, MSG>>,
Vec<&AttributeValue>,
Vec<&AttributeValue>,
) {
let mut callbacks = vec![];
let mut plain_values = vec![];
let mut func_values = vec![];
for av in attr.value() {
match av {
AttValue::Plain(plain) => {
if plain.is_function_call() {
func_values.push(plain);
} else {
plain_values.push(plain);
}
}
AttValue::Callback(cb) => callbacks.push(cb),
}
}
(callbacks, plain_values, func_values)
}
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) =
Self::partition_callbacks_from_plain_and_func_calls(attr);
if let Some(merged_plain_values) =
Self::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");
}
}
}
}
if let Some(merged_func_values) =
Self::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>,
) -> 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<_>>(),
);
let mut previous_node_was_text = false;
for child in velem.get_children().iter() {
match child {
crate::Node::Text(text_node) => {
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(&text_node))
.expect("Unable to append text node");
previous_node_was_text = true;
}
crate::Node::Element(element_node) => {
previous_node_was_text = false;
let child =
Self::create_element_node(program, element_node);
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
}
}