kagura 0.8.2

Frontend frame-work for wasm on Rust.
Documentation
use crate::event;
use crate::native;
use std::collections::HashSet;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys;

pub struct Renderer {
    before: super::Node,
    root: web_sys::Node,
}

impl Renderer {
    pub fn new(virtual_node: super::Node, root_node: web_sys::Node) -> Self {
        let root: web_sys::Node;
        if let Some(node) = render(&virtual_node, None, Some(&root_node)) {
            root = node;
        } else {
            root = native::create_text_node("").into();
        }
        let _ = root_node
            .parent_node()
            .expect("no parent node of root")
            .replace_child(&root, &root_node);
        Self {
            before: virtual_node,
            root,
        }
    }

    pub fn update(&mut self, after: super::Node) {
        if let Some(root) = render(&after, Some(&self.before), Some(&self.root)) {
            let _ = self
                .root
                .parent_node()
                .expect("no parent node of root")
                .replace_child(&root, &self.root);
            self.root = root;
        }
        self.before = after;
    }
}

fn set_attribute_all(element: &web_sys::Element, attributes: &super::Attributes) {
    for (a, v) in &attributes.attributes {
        if v.is_empty() {
            let _ = element.set_attribute(a, "");
        } else if let Some(d) = attributes.delimiters.get(a) {
            set_attribute_set(element, a, v, d);
        } else {
            set_attribute_set(element, a, v, "");
        }
    }
}

fn set_attribute_set(element: &web_sys::Element, a: &str, v: &HashSet<super::Value>, d: &str) {
    let v = v.iter().map(|v| v.into()).collect::<Vec<String>>();
    let v = v.iter().map(|v| &v as &str).collect::<Vec<&str>>().join(d);
    if String::from("value") == String::from(a) {
        if let Some(element) = element.dyn_ref::<web_sys::HtmlInputElement>() {
            element.set_value(&v);
        } else {
            let _ = element.set_attribute(a, &v);
        }
    } else {
        let _ = element.set_attribute(a, &v);
    }
}

fn set_event_all(element: &web_sys::Element, events: &super::Events) {
    for (t, hid) in &events.handlers {
        let hid = *hid;
        let h = Closure::once(move |e| {
            event::dispatch(hid, e);
        });
        let event_target: &web_sys::EventTarget = element.as_ref();
        let _ = event_target.add_event_listener_with_callback_and_add_event_listener_options(
            &t,
            h.as_ref().unchecked_ref(),
            web_sys::AddEventListenerOptions::new().once(true),
        );
        h.forget();
    }
}

fn set_attribute_diff(
    element: &web_sys::Element,
    after: &super::Attributes,
    before: &super::Attributes,
) {
    for (a, _) in &before.attributes {
        if after.attributes.get(a).is_none() {
            let _ = element.remove_attribute(a);
        }
    }
    set_attribute_all(element, after);
}

fn render(
    after: &super::Node,
    before: Option<&super::Node>,
    root: Option<&web_sys::Node>,
) -> Option<web_sys::Node> {
    use super::Node;
    match after {
        Node::Text(text) => Some(native::create_text_node(&text).into()),
        Node::Element(after) => {
            if after.need_rerendering {
                if let (Some(Node::Element(before)), Some(root)) = (before, root) {
                    if let Some(root) = root.dyn_ref::<web_sys::Element>() {
                        if after.tag_name == before.tag_name {
                            render_element_diff(after, before, root);
                            None
                        } else {
                            Some(render_element_force(after))
                        }
                    } else {
                        None
                    }
                } else {
                    Some(render_element_force(after))
                }
            } else {
                if let (Some(Node::Element(before)), Some(root)) = (before, root) {
                    render_element_lazy(after, before, root);
                    None
                } else {
                    None
                }
            }
        }
    }
}

fn render_element_lazy(after: &super::Element, before: &super::Element, root: &web_sys::Node) {
    let mut i: usize = 0;
    for child in &after.children {
        if let Some(b) = root.child_nodes().item(i as u32) {
            if let Some(a) = render(&child, before.children.get(i), Some(&b)) {
                let _ = root.replace_child(&a, &b);
            }
        }
        i += 1;
    }
}

fn render_element_force(after: &super::Element) -> web_sys::Node {
    let el = new_element(&after.tag_name, &after.attributes, &after.events);
    for child in &after.children {
        if let Some(node) = render(&child, None, None) {
            let _ = el.append_child(&node);
        }
    }
    el.into()
}

fn render_element_diff(after: &super::Element, before: &super::Element, root: &web_sys::Element) {
    for (_, hid) in &before.events.handlers {
        event::remove(hid);
    }
    set_attribute_diff(&root, &after.attributes, &before.attributes);
    set_event_all(&root, &after.events);
    let mut i = (root.child_nodes().length() as i64) - (after.children.len() as i64);
    while i > 0 {
        if let Some(node) = root
            .child_nodes()
            .item((after.children.len() as i64 + i - 1) as u32)
        {
            let _ = root.remove_child(&node);
        }
        i -= 1;
    }
    let mut i: usize = 0;
    for child in &after.children {
        if let Some(old) = root.child_nodes().item(i as u32) {
            if let Some(new) = render(&child, before.children.get(i), Some(&old)) {
                let _ = root.replace_child(&new, &old);
            }
        } else {
            if let Some(new) = render(&child, None, None) {
                let _ = root.append_child(&new);
            }
        }
        i += 1;
    }
}

fn new_element(
    tag_name: &str,
    attributes: &super::Attributes,
    events: &super::Events,
) -> web_sys::Element {
    let element = native::create_element(tag_name);
    set_attribute_all(&element, attributes);
    set_event_all(&element, events);
    element
}