use serde::{ Serialize, Deserialize };
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use linked_hash_map::LinkedHashMap;
use std::collections::HashMap;
use uuid::Uuid;
use crate::v2::driver::dom::component::DOMComponent;
use nom_html_parser::parser::tag::html;
use nom_html_parser::parser::tag::{
HtmlElement as NomHtmlElement,
};
use nom_html_parser::parser::utils::{
Node as NomNode,
};
use nom_html_parser::parser::attributes::{Attribute as NomAttribute};
pub mod component;
#[wasm_bindgen]
pub extern "C" {
#[wasm_bindgen(js_namespace = console)]
pub fn log(s: &str);
#[wasm_bindgen(js_namespace = console)]
pub fn error(s: &str);
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct AppEvent {
pub id: String,
pub event: String
}
fn clear_element(element: &web_sys::Element) {
while let Some(child) = element.last_child() {
element.remove_child(&child).expect("can't remove a child");
}
}
pub type EventHook = (String, String, gloo::events::EventListener);
pub type ParentNode = Option<web_sys::Node>;
pub fn window() -> web_sys::Window {
web_sys::window().expect("no window available")
}
pub fn document() -> web_sys::Document {
window().document().unwrap()
}
pub fn add_attributes<'a>(root_id: String, element_id: String, element: &web_sys::Element, nom_el: &NomHtmlElement, events: &'a mut HashMap<String, EventHook>) -> () {
for attribute in nom_el.attributes.iter() {
match attribute {
(NomAttribute::Class(_), value) => {
}
(NomAttribute::Custom(name), value) => {
match element.set_attribute(name, value) {
Err(e) => {
error(format!("could not set attribute {:?}", e).as_str());
}
_ => {}
}
}
(NomAttribute::Event(name), value) => {
let c_value = value.clone();
let clone_id = root_id.clone();
insert_and_get_event(
events,
None,
name.clone(),
value.clone(),
(
element,
Box::new(move |id| {
let app_event: AppEvent = AppEvent {
id: String::from(id),
event: c_value.clone()
};
let window = web_sys::window().expect("could not get window");
let event = web_sys::CustomEvent::new("app_message").unwrap();
event.init_custom_event_with_can_bubble_and_cancelable_and_detail(
format!("{}_app_message", clone_id).as_str(),
false,
true,
&JsValue::from_serde(&app_event).unwrap(),
);
match window.dispatch_event(&event) {
Err(e) => {
error(format!("could not send event: {:?}", e).as_str());
}
_ => {}
};
})
)
);
}
(NomAttribute::Component(_), value) => {
element.set_attribute("id", format!("component_{}_{}", value, element_id).as_str()).expect("could not set attribute");
}
}
}
}
pub fn insert_and_get_event<'a>(events: &'a mut HashMap<String, EventHook>, id: Option<String>, event_name: String, closure_name: String, (el, closure): (&'a web_sys::Element, Box<dyn Fn(&str) -> ()>)) -> (String, Option<&'a (String, String, gloo::events::EventListener)>) {
match id {
Some(id) => {
if events.contains_key(&id) {
(id.clone(), events.get(&id))
} else {
let r_id = id.clone();
events.insert(id.clone(), (event_name.clone(), closure_name, gloo::events::EventListener::new(el, event_name.clone(), move |_evt| { closure(&r_id); })));
(id.clone(), events.get(&id))
}
}
None => {
let n_id = Uuid::new_v4().to_string();
let r_id = n_id.clone();
events.insert(n_id.clone(), (event_name.clone(), closure_name, gloo::events::EventListener::new(el, event_name.clone(), move |_evt| { closure(&r_id); })));
(n_id.clone(), events.get(&n_id))
}
}
}
pub fn render_element(node: &web_sys::Node, template: &Option<(mustache::Template, Vec<NomNode>, String)>, input: &mustache::Data, nom_node: &NomNode, events: &mut HashMap<String, EventHook>) -> () {
match nom_node {
NomNode::Text(_s) => {
let rendered_data = template.clone();
match rendered_data {
Some((r, _, _)) => {
match r.render_data_to_string(&input) {
Ok(s) => {
let el = node.clone().dyn_into::<web_sys::Element>().ok().unwrap();
el.set_inner_html(s.as_ref());
}
Err(e) => {
error(format!("error in compiling template data: {:?}", e).as_ref());
}
}
}
None => {}
};
}
NomNode::Element(nom_node) | NomNode::Component(nom_node) => {
let el = node.clone().dyn_into::<web_sys::Element>().ok().unwrap();
let mut class_name: String = String::new();
for (index, (_, value)) in nom_node.attributes.iter().filter(|n| {
match n {
(NomAttribute::Class(_), _) => true,
_ => false,
}
}).enumerate() {
let tpl = mustache::compile_str(&value).unwrap();
let data = tpl.render_data_to_string(&input).unwrap();
if index > 0 && data != "" {
class_name.push_str(" ");
}
class_name.push_str(&data);
}
el.set_class_name(&class_name);
}
}
}
pub struct Driver<C: DOMComponent> {
pub id: String,
pub element: Option<web_sys::Element>,
nodes: LinkedHashMap<String, (web_sys::Element, ParentNode, NomNode, Option<(mustache::Template, Vec<NomNode>, String)>, usize, HashMap<String, EventHook>)>,
pub component: C,
pub shadow: bool,
}
impl<C: DOMComponent> Driver<C> {
pub fn new(shadow: bool) -> Self {
Driver {
id: Uuid::new_v4().to_string(),
element: None,
nodes: LinkedHashMap::new(),
component: C::create(),
shadow,
}
}
pub fn new_with_component(component: C, shadow: bool) -> Self {
Driver {
id: Uuid::new_v4().to_string(),
element: None,
nodes: LinkedHashMap::new(),
component,
shadow,
}
}
fn clean_nodes(&mut self, nodes: Vec<(NomNode, bool)>, root_id: String, parent_id: Option<String>) {
nodes.iter().enumerate().for_each(|(index, (n, to_destroy))| {
if to_destroy.clone() {
match n {
NomNode::Element(n) => {
let id_element = format!("{}-{}-{}-{}", root_id.clone(), n.tag.clone(), format!("{:?}", parent_id).as_str(), index).replace("\"", "").replace("\\", "");
if n.nodes.len() > 0 {
let nodes_to_destroy: Vec<(NomNode, bool)> = n.nodes.iter().map(|nn| (nn.clone(), true)).collect();
self.clean_nodes(nodes_to_destroy, root_id.clone(), Some(id_element.clone()));
}
match self.nodes.remove(&id_element) {
Some(_) => {
}
None => {
}
};
},
NomNode::Text(_) => {
let id_element = format!("{}-{}-{}-{}", root_id.clone(), format!("{:?}", parent_id), "dynamic_content", index).replace("\"", "").replace("\\", "");
match self.nodes.remove(&id_element) {
Some(_) => {
}
None => {
}
};
},
NomNode::Component(n) => {
let id_element = format!("{}-{}-{}-{}", root_id.clone(), n.tag.clone(), format!("{:?}", parent_id).as_str(), index).replace("\"", "").replace("\\", "");
self.component.destroy_child(id_element.clone());
match self.nodes.remove(&id_element) {
Some(_) => {
}
None => {
}
};
}
}
}
});
}
fn render_nom_element(
&mut self,
root_id: String,
parent: Option<web_sys::Node>,
parent_id: Option<String>,
childrens_data: &mustache::Data,
index: usize,
el: NomHtmlElement,
) {
let window = web_sys::window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
let id_element = format!("{}-{}-{}-{}", root_id.clone(), el.tag.clone(), format!("{:?}", parent_id).as_str(), index).replace("\"", "").replace("\\", "");
match self.nodes.get(&id_element) {
Some((n_el, _parent, _nom_node, tp_tuple, _index, _events)) => {
match tp_tuple {
Some((_template, _nodes, _tpl_str)) => {
}
None => {
let mut events: HashMap<String, EventHook> = HashMap::new();
let n_el = n_el.clone();
self.parse_tree(root_id.clone(), el.clone(), Some(n_el.clone().into()), Some(id_element.clone()), childrens_data);
add_attributes(root_id.clone(), id_element.clone(), &n_el, &el, &mut events);
}
}
}
None => {
let element = document.create_element(el.tag.as_str()).expect("could not create html element");
let n_node = self.parse_tree(root_id.clone(), el.clone(), Some(element.clone().into()), Some(id_element.clone()), childrens_data);
let mut events: HashMap<String, EventHook> = HashMap::new();
add_attributes(root_id.clone(), id_element.clone(), &element, &el, &mut events);
self.nodes.insert(id_element.clone(),
(
element.into(),
parent.clone(),
NomNode::Element(n_node),
None,
index,
events,
)
);
}
}
}
fn render_nom_text_element(
&mut self,
root_id: String,
parent: Option<web_sys::Node>,
parent_id: Option<String>,
childrens_data: &mustache::Data,
index: usize,
s: String,
child: NomNode,
) {
let window = web_sys::window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
let tpl = mustache::compile_str(&s).expect("could not compile template");
let rendered_html = tpl.render_data_to_string(&childrens_data).expect("could not render template");
let tree = match html(&rendered_html) {
Ok(s) => Ok(s.1),
Err(e) => {
Err(e)
}
};
let id_element = match tree.clone() {
Ok(tree_node) => {
format!("{}-{}-{}-{}", root_id.clone(), tree_node.tag.clone(), format!("{:?}", parent_id).as_str(), index).replace("\"", "").replace("\\", "")
},
Err(_) => {
format!("{}-{}-{}-{}", root_id.clone(), format!("{:?}", parent_id), "dynamic_content", index).replace("\"", "").replace("\\", "")
},
};
match self.nodes.get(&id_element) {
Some((el,_,_, m_template,_,_)) => {
match tree {
Ok(tree) => {
let c_el = el.clone();
let nodes = m_template.clone().unwrap().1;
let mut nodes_to_destroy: Vec<(NomNode, bool)> = vec![];
for (i, n) in nodes.clone().iter().enumerate() {
nodes_to_destroy.push((n.clone(), tree.nodes.get(i).is_none()));
};
let template = mustache::compile_str(&s).expect("could not create template");
self.clean_nodes(nodes_to_destroy.clone(), root_id.clone(), Some(id_element.clone()));
let mut events: HashMap<String, EventHook> = HashMap::new();
add_attributes(root_id.clone(), id_element.clone(), &c_el, &tree, &mut events);
self.parse_tree(root_id.clone(), tree.clone(), Some(c_el.clone().into()), Some(id_element.clone()), childrens_data);
if let Some(r_el) = self.nodes.get_mut(&id_element) {
let r_el_n_node = r_el.2.clone();
match r_el_n_node {
NomNode::Text(_) => {
clear_element(&c_el);
*r_el = (
c_el,
r_el.1.clone(),
child,
Some((template, tree.nodes.clone(), s.clone())),
r_el.4.clone(),
events,
);
}
_ => {}
};
}
}
Err(_e) => {}
}
}
None => {
match tree {
Ok(tree_node) => {
let element = document.create_element(tree_node.tag.as_str()).expect("could not create html element");
let template = mustache::compile_str(&s).expect("could not create template");
let mut events: HashMap<String, EventHook> = HashMap::new();
self.parse_tree(root_id.clone(), tree_node.clone(), Some(element.clone().into()), Some(id_element.clone()), childrens_data);
add_attributes(root_id.clone(), id_element.clone(), &element, &tree_node, &mut events);
self.nodes.insert(id_element.clone(),
(
element.into(),
parent.clone(),
child.clone(),
Some((template, tree_node.nodes.clone(), s.clone())),
index,
events,
)
);
}
Err(_e) => {
let dynamic_element_reference = format!("dynamic_content_{:?}_{:?}", id_element.clone(), index).replace("\"", "");
let element = document.create_element("span").expect("could not create html element");
element.set_inner_html(&s);
let mut class_name = dynamic_element_reference.clone();
class_name.push_str(" dynamic_content");
if class_name != "" {
element.set_class_name(class_name.as_str());
}
let template = mustache::compile_str(&s).expect("could not create template");
let events: HashMap<String, EventHook> = HashMap::new();
self.nodes.insert(id_element.clone(),
(
element.into(),
parent.clone(),
child.clone(),
Some((template, vec![], s.clone())),
index,
events,
)
);
}
}
}
}
}
fn render_nom_component_element(
&mut self,
root_id: String,
parent: Option<web_sys::Node>,
parent_id: Option<String>,
index: usize,
el: NomHtmlElement,
) {
let window = web_sys::window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
let id_element = format!("{}-{}-{}-{}", root_id.clone(), el.tag.clone(), format!("{:?}", parent_id).as_str(), index).replace("\"", "").replace("\\", "");
match self.nodes.get(&id_element) {
Some((n_el, _parent, _nom_node, tp_tuple, _index, _events)) => {
let c_el = n_el.clone();
match self.component.render_child(id_element.clone(), el.tag.clone(), Some(c_el.clone())) {
Ok(_) => {}
Err(_) => {}
};
}
None => {
let element = document.create_element("div").expect("could not create html element");
match self.component.render_child(id_element.clone(), el.tag.clone(), Some(element.clone())) {
Ok(_) => {
let mut events: HashMap<String, EventHook> = HashMap::new();
add_attributes(root_id.clone(), id_element.clone(), &element, &el, &mut events);
self.nodes.insert(id_element.clone(),
(
element.into(),
parent.clone(),
NomNode::Component(el),
None,
index,
events,
)
);
}
Err(_) => {
error(format!("component with id: {} already there", id_element).as_str());
}
}
}
}
}
pub fn parse_tree<'a>(
&mut self,
root_id: String,
mut node: NomHtmlElement,
parent: Option<web_sys::Node>,
parent_id: Option<String>,
childrens_data: &mustache::Data,
) -> NomHtmlElement {
let nodes = node.nodes.clone();
nodes.iter()
.enumerate()
.for_each(|(index, child)| {
match child {
NomNode::Element(el) => {
self.render_nom_element(root_id.clone(), parent.clone(), parent_id.clone(), childrens_data, index, el.clone());
},
NomNode::Text(s) => {
self.render_nom_text_element(root_id.clone(), parent.clone(), parent_id.clone(), childrens_data, index, s.clone(), child.clone());
}
NomNode::Component(el) => {
self.render_nom_component_element(
root_id.clone(),
parent.clone(),
parent_id.clone(),
index,
el.clone()
);
}
};
});
node
}
pub fn render(&mut self, input: Option<mustache::Data>) {
let window = web_sys::window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
let body = document.body().expect("document should have a body");
let input = input.unwrap_or(self.component.get_data().build());
let childrens = self.component.get_childrens().build();
let tree = match html(&self.component.view()) {
Ok(s) => Ok(s.1),
Err(e) => {
let err = format!("{:#?}", e);
error(err.clone().as_str());
Err(err)
}
}.expect("could not parse tree");
self.parse_tree(
"App".to_string(),
tree.clone(),
Some(self.element.clone().unwrap().into()),
None,
&childrens,
);
let mut events: HashMap<String, EventHook> = HashMap::new();
render_element(self.element.as_ref().unwrap(), &None, &input, &NomNode::Element(tree), &mut events);
let css_node = if self.shadow {
self.element.as_ref().unwrap().shadow_root().unwrap().query_selector("style").unwrap().unwrap()
} else {
self.element.as_ref().unwrap().query_selector("style").unwrap().unwrap()
};
let tpl = mustache::compile_str(&self.component.style()).expect("could not compile css template");
css_node.set_inner_html(tpl.render_data_to_string(&input).expect("could not render css data").as_str());
for (el_id, (node, parent, nom_node, template, index, events)) in self.nodes.iter_mut() {
match parent {
Some(parent) => {
let parent = parent.clone().dyn_into::<web_sys::Element>().ok().unwrap();
if parent.contains(Some(&node)) {
render_element(node, template, &input, nom_node, events);
} else {
if &parent == self.element.as_ref().unwrap() {
if self.shadow {
match parent.shadow_root().unwrap().append_child(&node) {
Err(e) => {
error(format!("failed to append node: {:?}", e).as_ref());
}
_ => {
render_element(node, template, &input, nom_node, events);
}
};
} else {
match parent.append_child(&node) {
Err(e) => {
error(format!("failed to append node: {:?}", e).as_ref());
}
_ => {
render_element(node, template, &input, nom_node, events);
}
};
}
} else {
match parent.append_child(&node) {
Err(e) => {
error(format!("failed to append node: {:?}", e).as_ref());
}
_ => {
render_element(node, template, &input, nom_node, events);
}
};
}
}
}
None => {
if body.contains(Some(node)) || self.element.as_ref().unwrap() == node {
render_element(&node, template, &input, nom_node, events);
} else {
match body.append_child(&node) {
Err(e) => {
error(format!("failed to append node: {:?}", e).as_ref());
}
_ => {
render_element(node, template, &input, nom_node, events);
}
};
}
}
}
};
}
pub fn mount(&mut self, parent: Option<web_sys::Node>, shadow: bool) -> NomHtmlElement {
let input = self.component.get_data().build();
let window = web_sys::window().expect("could not get window");
let document = window.document().expect("could not get document");
let tree = match html(self.component.view()) {
Ok(s) => Ok(s.1),
Err(e) => {
let err = format!("{:#?}", e);
error(err.clone().as_str());
Err(err)
}
}.expect("could not parse tree");
let element = document.create_element(tree.tag.clone().as_str()).expect("could not create HTML element");
let css_node = document.create_element("style").unwrap();
let tpl = mustache::compile_str(&self.component.style()).expect("could not compile css template");
css_node.set_inner_html(tpl.render_data_to_string(&input).expect("could not render css data").as_str());
if self.shadow {
let shadow = element
.attach_shadow(&web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open))
.unwrap();
shadow.append_with_node_1(&css_node).unwrap();
} else {
element.append_with_node_1(&css_node).unwrap();
};
match parent {
Some(parent) => {
let parent = parent.dyn_into::<web_sys::HtmlElement>().unwrap();
if shadow {
parent.shadow_root().unwrap().append_with_node_1(&element)
.map_err(|e| error(format!("{:?}", e).as_str()))
.expect("could not append to body");
} else {
parent.append_with_node_1(&element)
.map_err(|e| error(format!("{:?}", e).as_str()))
.expect("could not append to body");
};
parent
}
None => {
let body = document.body().expect("could not get body");
body.append_child(&element)
.map_err(|e| error(format!("{:?}", e).as_str()))
.expect("could not append to body");
body
}
};
self.element = Some(element);
self.render(None);
tree
}
pub fn get_data(&mut self) -> mustache::MapBuilder {
self.component.get_data()
}
pub fn is_owner_of_event(&self, evt: AppEvent) -> bool {
let mut is_owner = false;
for (_,_,_,_,_, events) in self.nodes.values() {
is_owner = events.get(&evt.id).is_some() || evt.id == self.id;
if is_owner {
break;
}
}
is_owner
}
pub fn receive(&mut self, evt: AppEvent) -> Result<(), String> {
let data = match DOMComponent::receive(&mut self.component, evt) {
Ok(_) => {
let data = self.component.get_data();
Some(data.build())
},
Err(e) => {
let data = self.component.get_data();
let data = data.insert("error", &e).unwrap().build();
Some(data)
},
};
self.render(data);
Ok(())
}
pub fn drop(&self) {
clear_element(self.element.as_ref().unwrap());
let element: web_sys::Node = self.element.clone().map(|e| e.into()).unwrap();
let parent = element.parent_node().unwrap();
parent.remove_child(&element)
.expect("could not delete element");
}
}