use super::super::{
At, AtValue, Attrs, CSSValue, EventHandler, EventHandlerManager, Node, SharedNodeWs, St, Style,
Tag, Text,
};
use crate::{
app::MessageMapper,
browser::{
dom::{virtual_dom_bridge, Namespace},
util,
},
};
use std::{borrow::Cow, fmt, fmt::Write as _, rc::Rc};
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ElKey(String);
#[allow(clippy::module_name_repetitions)]
pub fn el_key(key: &impl ToString) -> ElKey {
ElKey(key.to_string())
}
#[derive(Debug)] pub struct El<Ms> {
pub tag: Tag,
pub attrs: Attrs,
pub style: Style,
pub event_handler_manager: EventHandlerManager<Ms>,
pub children: Vec<Node<Ms>>,
pub namespace: Option<Namespace>,
pub node_ws: Option<web_sys::Node>,
pub refs: Vec<SharedNodeWs>,
pub key: Option<ElKey>,
pub insert_handlers: Vec<InsertEventHandler<Ms>>,
}
impl<Ms> Clone for El<Ms> {
fn clone(&self) -> Self {
Self {
tag: self.tag.clone(),
attrs: self.attrs.clone(),
style: self.style.clone(),
event_handler_manager: self.event_handler_manager.clone(),
children: self.children.clone(),
namespace: self.namespace.clone(),
node_ws: self.node_ws.clone(),
refs: self.refs.clone(),
key: self.key.clone(),
insert_handlers: vec![],
}
}
}
impl<Ms> fmt::Display for El<Ms> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let tag = self.tag.to_string();
let mut output = format!("<{}", &tag);
let mut attrs = self.attrs.clone();
let style = self.style.to_string();
if !style.is_empty() {
attrs.add(At::Style, style);
}
if let Some(namespace) = self.namespace.as_ref() {
attrs.add(At::Xmlns, namespace.as_str());
}
let attributes = attrs.to_string();
if !attributes.is_empty() {
let _ = write!(output, " {attributes}");
}
output += ">";
for child in &self.children {
output += &child.to_string();
}
let empty_elements = [
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param",
"source", "track", "wbr",
];
if !empty_elements.contains(&tag.to_lowercase().as_str()) {
let _ = write!(output, "</{}>", self.tag);
}
write!(f, "{output}")
}
}
impl<Ms: 'static, OtherMs: 'static> MessageMapper<Ms, OtherMs> for El<Ms> {
type SelfWithOtherMs = El<OtherMs>;
fn map_msg(self, f: impl FnOnce(Ms) -> OtherMs + 'static + Clone) -> El<OtherMs> {
El {
tag: self.tag,
attrs: self.attrs,
style: self.style,
children: self
.children
.into_iter()
.map(|c| c.map_msg(f.clone()))
.collect(),
node_ws: self.node_ws,
namespace: self.namespace,
event_handler_manager: self.event_handler_manager.map_msg(f),
refs: self.refs,
key: self.key,
insert_handlers: vec![],
}
}
}
impl<Ms: 'static, OtherMs: 'static> MessageMapper<Ms, OtherMs> for Vec<El<Ms>> {
type SelfWithOtherMs = Vec<El<OtherMs>>;
fn map_msg(self, f: impl FnOnce(Ms) -> OtherMs + 'static + Clone) -> Vec<El<OtherMs>> {
self.into_iter().map(|el| el.map_msg(f.clone())).collect()
}
}
impl<Ms> El<Ms> {
pub fn empty(tag: Tag) -> Self {
Self {
tag,
attrs: Attrs::empty(),
style: Style::empty(),
event_handler_manager: EventHandlerManager::new(),
children: Vec::new(),
namespace: None,
node_ws: None,
refs: Vec::new(),
key: None,
insert_handlers: vec![],
}
}
pub fn empty_svg(tag: Tag) -> Self {
let mut el = El::empty(tag);
el.namespace = Some(Namespace::Svg);
el
}
pub fn from_html(namespace: Option<&Namespace>, html: &str) -> Vec<Node<Ms>> {
let wrapper = util::document()
.create_element_ns(namespace.map(Namespace::as_str), "placeholder")
.expect("Problem creating web-sys element");
wrapper.set_inner_html(html);
let mut result = Vec::new();
let children = wrapper.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) = virtual_dom_bridge::node_from_ws(&child) {
result.push(child_vdom);
}
}
result
}
pub fn add_child(&mut self, element: Node<Ms>) -> &mut Self {
self.children.push(element);
self
}
pub fn add_attr(
&mut self,
key: impl Into<Cow<'static, str>>,
val: impl Into<AtValue>,
) -> &mut Self {
self.attrs.vals.insert(At::from(key), val.into());
self
}
pub fn add_class(&mut self, name: impl Into<Cow<'static, str>>) -> &mut Self {
let name = name.into();
self.attrs
.vals
.entry(At::Class)
.and_modify(|at_value| match at_value {
AtValue::Some(v) => {
if !v.is_empty() {
*v += " ";
}
*v += name.as_ref();
}
_ => *at_value = AtValue::Some(name.clone().into_owned()),
})
.or_insert(AtValue::Some(name.into_owned()));
self
}
pub fn add_style(&mut self, key: impl Into<St>, val: impl Into<CSSValue>) -> &mut Self {
self.style.vals.insert(key.into(), val.into());
self
}
pub fn add_event_handler(&mut self, event_handler: EventHandler<Ms>) -> &mut Self {
self.event_handler_manager
.add_event_handlers(vec![event_handler]);
self
}
pub fn add_text(&mut self, text: impl Into<Cow<'static, str>>) -> &mut Self {
self.children.push(Node::Text(Text::new(text)));
self
}
pub fn replace_text(&mut self, text: impl Into<Cow<'static, str>>) -> &mut Self {
self.children.retain(|node| !node.is_text());
self.children.push(Node::new_text(text));
self
}
pub fn get_text(&self) -> String {
self.children
.iter()
.filter_map(|child| match child {
Node::Text(text_node) => Some(text_node.text.to_string()),
_ => None,
})
.collect()
}
#[cfg(debug_assertions)]
pub fn warn_about_script_tags(&self) {
let script_found = match &self.tag {
Tag::Script => true,
Tag::Custom(tag) if tag == "script" => true,
_ => false,
};
if script_found {
web_sys::console::error_1(&wasm_bindgen::JsValue::from("Script tag found inside mount point! \
Please check https://docs.rs/seed/latest/seed/app/builder/struct.Builder.html#examples"));
}
for child in &self.children {
child.warn_about_script_tags();
}
}
pub fn strip_ws_nodes_from_self_and_children(&mut self) {
self.node_ws.take();
for child in &mut self.children {
child.strip_ws_nodes_from_self_and_children();
}
}
pub const fn is_custom(&self) -> bool {
matches!(self.tag, Tag::Custom(_))
}
}
pub struct InsertEventHandler<Ms>(pub(crate) Rc<dyn Fn(web_sys::Element) -> Option<Ms>>);
impl<Ms> fmt::Debug for InsertEventHandler<Ms> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "InsertEventHandler")
}
}
pub fn on_insert<Ms: 'static, MsU: 'static>(
handler: impl FnOnce(web_sys::Element) -> MsU + 'static + Clone,
) -> InsertEventHandler<Ms> {
let handler = map_callback_return_to_option_ms!(
dyn Fn(web_sys::Element) -> Option<Ms>,
handler.clone(),
"Handler can return only Msg, Option<Msg> or ()!",
Rc
);
InsertEventHandler(handler)
}