use super::Namespace;
use crate::virtual_dom::{At, AtValue, Attrs, El, Mailbox, Node, Style, Text};
use std::borrow::Cow;
use std::cmp::Ordering;
use wasm_bindgen::JsCast;
use web_sys::Document;
fn set_style(el_ws: &web_sys::Node, style: &Style) {
el_ws
.dyn_ref::<web_sys::Element>()
.expect("Problem casting Node as Element while setting style")
.set_attribute("style", &style.to_string())
.expect("Problem setting style");
}
pub(crate) fn assign_ws_nodes_to_el<Ms>(document: &Document, el: &mut El<Ms>) {
let node_ws = make_websys_el(el, document);
el.node_ws = Some(node_ws);
for child in &mut el.children {
assign_ws_nodes(document, child);
}
}
pub(crate) fn assign_ws_nodes_to_text(document: &Document, text: &mut Text) {
text.node_ws = Some(
document
.create_text_node(&text.text)
.dyn_into::<web_sys::Node>()
.expect("Problem casting Text as Node."),
);
}
pub(crate) fn assign_ws_nodes<Ms>(document: &Document, node: &mut Node<Ms>) {
match node {
Node::Element(el) => assign_ws_nodes_to_el(document, el),
Node::Text(text) => assign_ws_nodes_to_text(document, text),
Node::Empty | Node::NoChange => (),
}
}
fn node_to_element(el_ws: &web_sys::Node) -> Result<&web_sys::Element, Cow<str>> {
if el_ws.node_type() == web_sys::Node::ELEMENT_NODE {
el_ws
.dyn_ref::<web_sys::Element>()
.ok_or_else(|| Cow::from("Problem casting Node as Element"))
} else {
Err(Cow::from("Node isn't Element!"))
}
}
fn set_attr_value(el_ws: &web_sys::Node, at: &At, at_value: &AtValue) {
match at_value {
AtValue::Some(value) => {
node_to_element(el_ws)
.and_then(|element| {
element.set_attribute(at.as_str(), value).map_err(|error| {
Cow::from(format!("Problem setting an attribute: {error:?}"))
})
})
.unwrap_or_else(|err| {
crate::error(err);
});
}
AtValue::None => {
node_to_element(el_ws)
.and_then(|element| {
element.set_attribute(at.as_str(), "").map_err(|error| {
Cow::from(format!("Problem setting an attribute: {error:?}"))
})
})
.unwrap_or_else(|err| {
crate::error(err);
});
}
AtValue::Ignored => {
node_to_element(el_ws)
.and_then(|element| {
element.remove_attribute(at.as_str()).map_err(|error| {
Cow::from(format!("Problem removing an attribute: {error:?}"))
})
})
.unwrap_or_else(|err| {
crate::error(err);
});
}
}
}
pub(crate) fn make_websys_el<Ms>(el: &mut El<Ms>, document: &web_sys::Document) -> web_sys::Node {
let tag = el.tag.as_str();
let el_ws = el.namespace.as_ref().map_or_else(
|| {
document
.create_element(tag)
.expect("Problem creating web-sys element")
},
|ns| {
document
.create_element_ns(Some(ns.as_str()), tag)
.expect("Problem creating web-sys element with namespace")
},
);
fix_attrs_order(&mut el.attrs);
for (at, attr_value) in &el.attrs.vals {
set_attr_value(&el_ws, at, attr_value);
}
if let Some(ns) = &el.namespace {
el_ws
.dyn_ref::<web_sys::Element>()
.expect("Problem casting Node as Element while setting an attribute")
.set_attribute("xmlns", ns.as_str())
.expect("Problem setting xlmns attribute");
}
if el.style.vals.keys().len() > 0 {
set_style(&el_ws, &el.style);
}
el_ws.into()
}
pub fn attach_text_node(text: &mut Text, parent: &web_sys::Node) {
let node_ws = text.node_ws.take().expect("Missing websys node for Text");
parent
.append_child(&node_ws)
.expect("Problem appending text node");
text.node_ws.replace(node_ws);
}
pub fn attach_children<Ms>(
children: &mut [Node<Ms>],
parent: &web_sys::Node,
mailbox: &Mailbox<Ms>,
) {
for child in children.iter_mut() {
match child {
Node::Element(child_el) => attach_el_and_children(child_el, parent, mailbox),
Node::Text(child_text) => attach_text_node(child_text, parent),
Node::Empty | Node::NoChange => (),
}
}
}
pub fn attach_el_and_children<Ms>(el: &mut El<Ms>, parent: &web_sys::Node, mailbox: &Mailbox<Ms>) {
let el_ws = el
.node_ws
.as_ref()
.expect("Missing websys el in attach_el_and_children");
if parent.append_child(el_ws).is_err() {
crate::error("Minor problem with html element (append)");
}
attach_children(&mut el.children, el_ws, mailbox);
set_default_element_state(el_ws, el);
wire_up_el(el, mailbox);
}
fn set_default_element_state<Ms>(el_ws: &web_sys::Node, el: &El<Ms>) {
if let Some(at_value) = el.attrs.vals.get(&At::AutoFocus) {
match at_value {
AtValue::Some(_) | AtValue::None => el_ws
.dyn_ref::<web_sys::HtmlElement>()
.expect("Problem casting Node as HtmlElement while focusing")
.focus()
.expect("Problem focusing to an element."),
AtValue::Ignored => (),
}
}
if let Some(textarea) = el_ws.dyn_ref::<web_sys::HtmlTextAreaElement>() {
if let Some(AtValue::Some(value)) = el.attrs.vals.get(&At::Value) {
textarea.set_value(value);
}
}
}
pub fn _remove_children(el: &web_sys::Node) {
while let Some(child) = el.last_child() {
el.remove_child(&child).expect("Problem removing child");
}
}
pub(crate) fn patch_el_details<Ms>(
old: &mut El<Ms>,
new: &mut El<Ms>,
old_el_ws: &web_sys::Node,
mailbox: &Mailbox<Ms>,
) {
fix_attrs_order(&mut new.attrs);
for (key, new_val) in &new.attrs.vals {
old.attrs.vals.get(key).map_or_else(
|| {
set_attr_value(old_el_ws, key, new_val);
},
|old_val| {
if old_val != new_val {
set_attr_value(old_el_ws, key, new_val);
}
},
);
match key {
At::Value => match new_val {
AtValue::Some(new_val) => crate::util::set_value(old_el_ws, new_val),
AtValue::None | AtValue::Ignored => crate::util::set_value(old_el_ws, ""),
},
At::Checked => match new_val {
AtValue::Some(_) | AtValue::None => crate::util::set_checked(old_el_ws, true),
AtValue::Ignored => crate::util::set_checked(old_el_ws, false),
},
_ => Ok(()),
}
.unwrap_or_else(|err| {
crate::error(err);
});
}
for (key, old_val) in &old.attrs.vals {
if new.attrs.vals.get(key).is_none() {
old_el_ws.dyn_ref::<web_sys::Element>().map_or_else(
|| {
crate::error("Minor error on html element (setting attrs)");
},
|el| {
el.remove_attribute(key.as_str())
.expect("Removing an attribute");
match key {
At::Value => match old_val {
AtValue::Some(_) => crate::util::set_value(old_el_ws, ""),
_ => Ok(()),
},
At::Checked => match old_val {
AtValue::Some(_) | AtValue::None => {
crate::util::set_checked(old_el_ws, false)
}
AtValue::Ignored => Ok(()),
},
_ => Ok(()),
}
.unwrap_or_else(|err| {
crate::error(err);
});
},
);
}
}
new.event_handler_manager.attach_listeners(
old_el_ws.clone(),
Some(&mut old.event_handler_manager),
mailbox,
);
if old.style != new.style {
set_style(old_el_ws, &new.style);
}
}
#[allow(clippy::match_same_arms)]
fn fix_attrs_order(attrs: &mut Attrs) {
attrs.vals.sort_by(|at_a, _, at_b, _| {
match (at_a, at_b) {
(At::Value, At::Value) => Ordering::Equal,
(At::Value, _) => Ordering::Greater,
(_, At::Value) => Ordering::Less,
_ => Ordering::Equal,
}
});
}
#[allow(clippy::too_many_lines)]
impl<Ms> From<&web_sys::Element> for El<Ms> {
#[allow(clippy::too_many_lines)]
fn from(ws_el: &web_sys::Element) -> Self {
let namespace = ws_el.namespace_uri().map(Namespace::from);
let mut el = match namespace {
Some(Namespace::Html) => El::empty(ws_el.tag_name().to_lowercase().into()),
_ => El::empty(ws_el.tag_name().into()),
};
let mut attrs = Attrs::empty();
ws_el
.get_attribute_names()
.for_each(&mut |attr_name, _, _| {
let attr_name = attr_name
.as_string()
.expect("problem converting attr to string");
if let Some(attr_val) = ws_el.get_attribute(&attr_name) {
attrs.add(attr_name.into(), &attr_val);
}
});
el.attrs = attrs;
let svg_tags = [
"line",
"rect",
"circle",
"ellipse",
"polygon",
"polyline",
"mesh",
"path",
"defs",
"g",
"marker",
"mask",
"pattern",
"svg",
"switch",
"symbol",
"unknown",
"linearGradient",
"radialGradient",
"meshGradient",
"stop",
"image",
"use",
"altGlyph",
"altGlyphDef",
"altGlyphItem",
"glyph",
"glyphRef",
"textPath",
"text",
"tref",
"tspan",
"clipPath",
"cursor",
"filter",
"foreignObject",
"hathpath",
"meshPatch",
"meshRow",
"view",
"colorProfile",
"animate",
"animateColor",
"animateMotion",
"animateTransform",
"discard",
"mpath",
"set",
"desc",
"metadata",
"title",
"feBlend",
"feColorMatrix",
"feComponentTransfer",
"feComposite",
"feConvolveMatrix",
"feDiffuseLighting",
"feDisplacementMap",
"feDropShadow",
"feFlood",
"feFuncA",
"feFuncB",
"feFuncG",
"feFuncR",
"feGaussianBlur",
"feImage",
"feMerge",
"feMergeNode",
"feMorphology",
"feOffset",
"feSpecularLighting",
"feTile",
"feTurbulence",
"font",
"hkern",
"vkern",
"hatch",
"solidcolor",
];
if svg_tags.contains(&ws_el.tag_name().as_str()) {
el.namespace = Some(Namespace::Svg);
}
if let Some(ref ns) = namespace {
if ns != &Namespace::Html {
el.namespace = namespace;
}
}
let children = ws_el.child_nodes();
for i in 0..children.length() {
let child = children
.get(i)
.expect("Can't find child in raw html element.");
if let Some(child_vdom) = node_from_ws(&child) {
el.children.push(child_vdom);
}
}
el
}
}
impl<Ms> From<&web_sys::Element> for Node<Ms> {
fn from(ws_el: &web_sys::Element) -> Node<Ms> {
Node::Element(ws_el.into())
}
}
pub fn node_from_ws<Ms>(node: &web_sys::Node) -> Option<Node<Ms>> {
match node.node_type() {
web_sys::Node::ELEMENT_NODE => {
let ws_el = node
.dyn_ref::<web_sys::Element>()
.expect("Problem casting Node as Element");
Some(ws_el.into())
}
web_sys::Node::TEXT_NODE => Some(Node::new_text(
node.text_content().expect("Can't find text"),
)),
web_sys::Node::COMMENT_NODE => None,
node_type => {
crate::error(format!(
"HTML node type {node_type} is not supported by Seed"
));
None
}
}
}
pub(crate) fn insert_el_and_children<Ms>(
el: &mut El<Ms>,
parent: &web_sys::Node,
next: Option<web_sys::Node>,
mailbox: &Mailbox<Ms>,
) {
let el_ws = el.node_ws.take().expect("Missing websys el in insert_el");
insert_node(&el_ws, parent, next);
attach_children(&mut el.children, &el_ws, mailbox);
el.node_ws.replace(el_ws);
wire_up_el(el, mailbox);
}
pub(crate) fn insert_node(
node: &web_sys::Node,
parent: &web_sys::Node,
next: Option<web_sys::Node>,
) {
next.map_or_else(
|| {
parent.append_child(node).expect("Problem inserting node");
},
|n| {
parent
.insert_before(node, Some(&n))
.expect("Problem inserting node");
},
);
}
pub(crate) fn remove_node(node: &web_sys::Node, parent: &web_sys::Node) {
parent
.remove_child(node)
.expect("Problem removing old el_ws when updating to empty");
}
pub(crate) fn replace_child(new: &web_sys::Node, old: &web_sys::Node, parent: &web_sys::Node) {
parent
.replace_child(new, old)
.expect("Problem replacing element");
}
#[inline]
fn wire_up_el<Ms>(el: &mut El<Ms>, mailbox: &Mailbox<Ms>) {
let node_ws = el
.node_ws
.as_ref()
.expect("Missing websys el in attach_el_and_children");
for ref_ in &mut el.refs {
ref_.set(node_ws.clone());
}
el.event_handler_manager
.attach_listeners(node_ws.clone(), None, mailbox);
for handler in &el.insert_handlers {
let el_ws = node_ws
.dyn_ref::<web_sys::Element>()
.expect("Problem casting Node as Element while wiring up el");
let maybe_msg = handler.0(el_ws.clone());
mailbox.send(maybe_msg);
}
}