use super::{Attributes, Classes, Listener, Listeners, Patch, Reform, VDiff, VNode};
use crate::html::{Component, Scope};
use log::warn;
use std::borrow::Cow;
use std::cmp::PartialEq;
use std::collections::HashSet;
use std::fmt;
use stdweb::unstable::TryFrom;
use stdweb::web::html_element::InputElement;
use stdweb::web::html_element::TextAreaElement;
use stdweb::web::{document, Element, EventListenerHandle, IElement, INode, Node};
#[allow(unused_imports)]
use stdweb::{_js_impl, js};
pub const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
pub const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml";
pub struct VTag<COMP: Component> {
tag: Cow<'static, str>,
pub reference: Option<Element>,
pub listeners: Listeners<COMP>,
pub attributes: Attributes,
pub childs: Vec<VNode<COMP>>,
pub classes: Classes,
pub value: Option<String>,
pub kind: Option<String>,
pub checked: bool,
captured: Vec<EventListenerHandle>,
}
impl<COMP: Component> VTag<COMP> {
pub fn new<S: Into<Cow<'static, str>>>(tag: S) -> Self {
VTag {
tag: tag.into(),
reference: None,
classes: Classes::new(),
attributes: Attributes::new(),
listeners: Vec::new(),
captured: Vec::new(),
childs: Vec::new(),
value: None,
kind: None,
checked: false,
}
}
pub fn tag(&self) -> &str {
&self.tag
}
pub fn add_child(&mut self, child: VNode<COMP>) {
self.childs.push(child);
}
pub fn add_children(&mut self, children: Vec<VNode<COMP>>) {
for child in children {
self.childs.push(child);
}
}
pub fn add_class(&mut self, class: &str) {
let class = class.trim();
if !class.is_empty() {
self.classes.insert(class.into());
}
}
pub fn add_classes(&mut self, classes: Vec<&str>) {
for class in classes {
let class = class.trim();
if !class.is_empty() {
self.classes.insert(class.into());
}
}
}
pub fn set_classes(&mut self, classes: &str) {
self.classes = classes.split_whitespace().map(String::from).collect();
}
pub fn set_value<T: ToString>(&mut self, value: &T) {
self.value = Some(value.to_string());
}
pub fn set_kind<T: ToString>(&mut self, value: &T) {
self.kind = Some(value.to_string());
}
pub fn set_checked(&mut self, value: bool) {
self.checked = value;
}
pub fn add_attribute<T: ToString>(&mut self, name: &str, value: &T) {
self.attributes.insert(name.to_owned(), value.to_string());
}
pub fn add_attributes(&mut self, attrs: Vec<(String, String)>) {
for (name, value) in attrs {
self.attributes.insert(name, value);
}
}
pub fn add_listener(&mut self, listener: Box<dyn Listener<COMP>>) {
self.listeners.push(listener);
}
pub fn add_listeners(&mut self, listeners: Vec<Box<dyn Listener<COMP>>>) {
for listener in listeners {
self.listeners.push(listener);
}
}
fn diff_classes(&mut self, ancestor: &mut Option<Self>) -> Vec<Patch<String, ()>> {
let mut changes = Vec::new();
if let Some(ref ancestor) = ancestor {
let to_add = self
.classes
.difference(&ancestor.classes)
.map(|class| Patch::Add(class.to_owned(), ()));
changes.extend(to_add);
let to_remove = ancestor
.classes
.difference(&self.classes)
.map(|class| Patch::Remove(class.to_owned()));
changes.extend(to_remove);
} else {
let to_add = self
.classes
.iter()
.map(|class| Patch::Add(class.to_owned(), ()));
changes.extend(to_add);
}
changes
}
fn diff_attributes(&mut self, ancestor: &mut Option<Self>) -> Vec<Patch<String, String>> {
let mut changes = Vec::new();
if let Some(ref mut ancestor) = ancestor {
let self_keys = self.attributes.keys().collect::<HashSet<_>>();
let ancestor_keys = ancestor.attributes.keys().collect::<HashSet<_>>();
let to_add = self_keys.difference(&ancestor_keys).map(|key| {
let value = self.attributes.get(*key).expect("attribute of vtag lost");
Patch::Add(key.to_string(), value.to_string())
});
changes.extend(to_add);
for key in self_keys.intersection(&ancestor_keys) {
let self_value = self
.attributes
.get(*key)
.expect("attribute of self side lost");
let ancestor_value = ancestor
.attributes
.get(*key)
.expect("attribute of ancestor side lost");
if self_value != ancestor_value {
let mutator = Patch::Replace(key.to_string(), self_value.to_string());
changes.push(mutator);
}
}
let to_remove = ancestor_keys
.difference(&self_keys)
.map(|key| Patch::Remove(key.to_string()));
changes.extend(to_remove);
} else {
for (key, value) in &self.attributes {
let mutator = Patch::Add(key.to_string(), value.to_string());
changes.push(mutator);
}
}
changes
}
fn diff_kind(&mut self, ancestor: &mut Option<Self>) -> Option<Patch<String, ()>> {
match (
&self.kind,
ancestor.as_mut().and_then(|anc| anc.kind.take()),
) {
(&Some(ref left), Some(ref right)) => {
if left != right {
Some(Patch::Replace(left.to_string(), ()))
} else {
None
}
}
(&Some(ref left), None) => Some(Patch::Add(left.to_string(), ())),
(&None, Some(right)) => Some(Patch::Remove(right)),
(&None, None) => None,
}
}
fn diff_value(&mut self, ancestor: &mut Option<Self>) -> Option<Patch<String, ()>> {
match (
&self.value,
ancestor.as_mut().and_then(|anc| anc.value.take()),
) {
(&Some(ref left), Some(ref right)) => {
if left != right {
Some(Patch::Replace(left.to_string(), ()))
} else {
None
}
}
(&Some(ref left), None) => Some(Patch::Add(left.to_string(), ())),
(&None, Some(right)) => Some(Patch::Remove(right)),
(&None, None) => None,
}
}
fn apply_diffs(&mut self, element: &Element, ancestor: &mut Option<Self>) {
let changes = self.diff_classes(ancestor);
for change in changes {
let list = element.class_list();
match change {
Patch::Add(class, _) | Patch::Replace(class, _) => {
list.add(&class).expect("can't add a class");
}
Patch::Remove(class) => {
list.remove(&class).expect("can't remove a class");
}
}
}
let changes = self.diff_attributes(ancestor);
for change in changes {
match change {
Patch::Add(key, value) | Patch::Replace(key, value) => {
set_attribute(element, &key, &value);
}
Patch::Remove(key) => {
remove_attribute(element, &key);
}
}
}
if let Ok(input) = InputElement::try_from(element.clone()) {
if let Some(change) = self.diff_kind(ancestor) {
match change {
Patch::Add(kind, _) | Patch::Replace(kind, _) => {
let input = &input;
js! { @(no_return)
@{input}.type = @{kind};
}
}
Patch::Remove(_) => {
let input = &input;
js! { @(no_return)
@{input}.type = "";
}
}
}
}
if let Some(change) = self.diff_value(ancestor) {
match change {
Patch::Add(kind, _) | Patch::Replace(kind, _) => {
input.set_raw_value(&kind);
}
Patch::Remove(_) => {
input.set_raw_value("");
}
}
}
set_checked(&input, self.checked);
} else if let Ok(tae) = TextAreaElement::try_from(element.clone()) {
if let Some(change) = self.diff_value(ancestor) {
match change {
Patch::Add(value, _) | Patch::Replace(value, _) => {
tae.set_value(&value);
}
Patch::Remove(_) => {
tae.set_value("");
}
}
}
}
}
}
impl<COMP: Component> VDiff for VTag<COMP> {
type Component = COMP;
fn detach(&mut self, parent: &Element) -> Option<Node> {
let node = self
.reference
.take()
.expect("tried to remove not rendered VTag from DOM");
let sibling = node.next_sibling();
if parent.remove_child(&node).is_err() {
warn!("Node not found to remove VTag");
}
sibling
}
fn apply(
&mut self,
parent: &Element,
precursor: Option<&Node>,
ancestor: Option<VNode<Self::Component>>,
env: &Scope<Self::Component>,
) -> Option<Node> {
assert!(
self.reference.is_none(),
"reference is ignored so must not be set"
);
let (reform, mut ancestor) = {
match ancestor {
Some(VNode::VTag(mut vtag)) => {
if self.tag == vtag.tag {
self.reference = vtag.reference.take();
(Reform::Keep, Some(vtag))
} else {
let node = vtag.detach(parent);
(Reform::Before(node), None)
}
}
Some(mut vnode) => {
let node = vnode.detach(parent);
(Reform::Before(node), None)
}
None => (Reform::Before(None), None),
}
};
match reform {
Reform::Keep => {}
Reform::Before(before) => {
let element = if self.tag == "svg"
|| parent.namespace_uri() == Some(SVG_NAMESPACE.to_string())
{
document()
.create_element_ns(SVG_NAMESPACE, &self.tag)
.expect("can't create namespaced element for vtag")
} else {
document()
.create_element(&self.tag)
.expect("can't create element for vtag")
};
if let Some(sibling) = before {
parent
.insert_before(&element, &sibling)
.expect("can't insert tag before sibling");
} else {
let precursor = precursor.and_then(|before| before.next_sibling());
if let Some(precursor) = precursor {
parent
.insert_before(&element, &precursor)
.expect("can't insert tag before precursor");
} else {
parent.append_child(&element);
}
}
self.reference = Some(element);
}
}
let element = self.reference.clone().expect("element expected");
{
let mut ancestor_childs = Vec::new();
if let Some(ref mut a) = ancestor {
std::mem::swap(&mut ancestor_childs, &mut a.childs);
}
self.apply_diffs(&element, &mut ancestor);
if let Some(mut ancestor) = ancestor {
for handle in ancestor.captured.drain(..) {
handle.remove();
}
}
for mut listener in self.listeners.drain(..) {
let handle = listener.attach(&element, env.clone());
self.captured.push(handle);
}
let mut precursor = None;
let mut self_childs = self.childs.iter_mut();
let mut ancestor_childs = ancestor_childs.drain(..);
loop {
match (self_childs.next(), ancestor_childs.next()) {
(Some(left), right) => {
precursor = left.apply(&element, precursor.as_ref(), right, &env);
}
(None, Some(ref mut right)) => {
right.detach(&element);
}
(None, None) => break,
}
}
}
self.reference.as_ref().map(|e| e.as_node().to_owned())
}
}
impl<COMP: Component> fmt::Debug for VTag<COMP> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "VTag {{ tag: {} }}", self.tag)
}
}
fn set_attribute(element: &Element, name: &str, value: &str) {
js!( @(no_return) @{element}.setAttribute( @{name}, @{value} ); );
}
fn remove_attribute(element: &Element, name: &str) {
js!( @(no_return) @{element}.removeAttribute( @{name} ); );
}
fn set_checked(input: &InputElement, value: bool) {
js!( @(no_return) @{input}.checked = @{value}; );
}
impl<COMP: Component> PartialEq for VTag<COMP> {
fn eq(&self, other: &VTag<COMP>) -> bool {
if self.tag != other.tag {
return false;
}
if self.value != other.value {
return false;
}
if self.kind != other.kind {
return false;
}
if self.checked != other.checked {
return false;
}
if self.listeners.len() != other.listeners.len() {
return false;
}
for i in 0..self.listeners.len() {
let a = &self.listeners[i];
let b = &other.listeners[i];
if a.kind() != b.kind() {
return false;
}
}
if self.attributes != other.attributes {
return false;
}
if self.classes.iter().ne(other.classes.iter()) {
return false;
}
if self.childs.len() != other.childs.len() {
return false;
}
for i in 0..self.childs.len() {
let a = &self.childs[i];
let b = &other.childs[i];
if a != b {
return false;
}
}
true
}
}