mika 0.0.0

A framework for building wasm front-end web application in Rust
use wasm_bindgen::JsCast;
use wasm_bindgen::UnwrapThrowExt;

use super::{spawn_for_each, create_text_node, InputType};

pub trait Attachable {
    fn append_to(&self, parent: &web_sys::Node);
    fn insert_at(&self, index: usize, parent: &web_sys::Node);
    fn replace_at(&self, index: usize, parent: &web_sys::Node);
    fn remove_from(&self, parent: &web_sys::Node);
}

pub trait Element: Sized {
    fn websys_element(&self) -> &web_sys::Element;
    fn websys_node(&self) -> &web_sys::Node;
    // TODO: Store all future from signal and stop them when require
    fn store_future(&mut self);
    fn listeners_mut(&mut self) -> &mut Vec<Box<crate::events::Listener>>;
}

// TODO: Implement Drop for T: Element?

impl<T: Element> Attachable for T {
    fn append_to(&self, parent: &web_sys::Node) {
        parent
            .append_child(self.websys_node())
            .expect_throw("parent.append_child(self.websys_node())");
    }
    fn insert_at(&self, index: usize, parent: &web_sys::Node) {
        let node_at_index = parent.child_nodes().get(index as u32);
        parent
            .insert_before(self.websys_node(), node_at_index.as_ref())
            .expect_throw("parent.insert_before(self.websys_node(), node_at_index.as_ref())");
    }
    fn replace_at(&self, index: usize, parent: &web_sys::Node) {
        let node_at_index = parent
            .child_nodes()
            .get(index as u32)
            .expect_throw("child_nodes().get");
        parent
            .replace_child(self.websys_node(), &node_at_index)
            .expect_throw("parent.replace_child(self.websys_node(), &node_at_index)");
    }
    fn remove_from(&self, parent: &web_sys::Node) {
        parent
            .remove_child(self.websys_node())
            .expect_throw("parent.remove_child(self.websys_node())");
    }
}

#[cfg(feature = "message-like-elm")]
macro_rules! implement_events {
    ($($method_name:ident $WebSysEventArgType:ident $EventType:ident,)+) => {
        $(
            fn $method_name<A, F>(mut self, main: &crate::app::WeakMain<A>, hanlder: F) -> Self
            where
                A: crate::app::Application,
                F: Fn(web_sys::$WebSysEventArgType) -> A::Message + 'static
            {
                let e = crate::events::$EventType::on(self.websys_node(), main, hanlder);
                self.listeners_mut().push(e);
                self
            }
        )+
    }
}

#[cfg(not(feature = "message-like-elm"))]
macro_rules! implement_events {
    ($($method_name:ident $WebSysEventArgType:ident $EventType:ident,)+) => {
        $(
            fn $method_name<F>(mut self, hanlder: F) -> Self
            where
                F: Fn(web_sys::$WebSysEventArgType) + 'static
            {
                let e = crate::events::$EventType::on(self.websys_node(), hanlder);
                self.listeners_mut().push(e);
                self
            }
        )+
    }
}

pub trait GlobalAttributes: Element {
    fn id(self, value: &str) -> Self {
        self.websys_element().set_id(value);
        self
    }

    // Should this be in here or in HtmlElement?
    fn focus_signal<S>(self, signal: S) -> Self
    where
        S: signals::signal::Signal<Item = bool> + 'static,
    {
        let ws_element: web_sys::HtmlElement = self
            .websys_element()
            .clone()
            .dyn_into()
            .expect_throw("self.websys_element().dyn_into()");
        spawn_for_each(signal, move |value| {
            if value {
                if let Err(e) = ws_element.focus() {
                    log::error!("{:?}", e);
                }
            }
        });
        self
    }

    fn class(self, space_separated_classes: &str) -> Self {
        self.websys_element()
            .set_attribute("class", space_separated_classes)
            .expect_throw(
                "self.websys_element().set_attribute(\"class\", space_separated_classes)",
            );
        self
    }

    fn class_signal<S>(self, class_name: &str, signal: S) -> Self
    where
        S: signals::signal::Signal<Item = bool> + 'static,
    {
        let class_name = class_name.to_string();
        let ws_element = self.websys_element().clone();
        spawn_for_each(signal, move |value| {
            if value {
                ws_element
                    .class_list()
                    .add_1(&class_name)
                    .expect_throw("ws_element.class_list().add_1(&class_name)");
            } else {
                ws_element
                    .class_list()
                    .remove_1(&class_name)
                    .expect_throw("ws_element.class_list().remove_1(&class_name)");
            }
        });
        self
    }

    implement_events! {
        on_blur FocusEvent Blur,
        on_focus FocusEvent Focus,
        on_click MouseEvent Click,
        on_double_click MouseEvent DoubleClick,
        on_change Event Change,
        on_key_press KeyboardEvent KeyPress,
    }
}

pub trait TextContent: Element {
    fn text(self, text: &str) -> Self {
        let ws_node = self.websys_node();
        let text = create_text_node(text);
        ws_node
            .append_child(&text)
            .expect_throw("ws_node.append_child(&text)");
        self
    }

    // TODO: More efficient for things that is already a string such as: str, String...
    fn text_signal<S, T>(self, signal: S) -> Self
    where
        T: ToString + 'static,
        S: signals::signal::Signal<Item = T> + 'static,
    {
        let ws_node = self.websys_node();
        let text = create_text_node("");
        ws_node
            .append_child(&text)
            .expect_throw("ws_node.append_child(&text)");
        spawn_for_each(signal, move |value| {
            text.set_text_content(Some(&value.to_string()));
        });
        self
    }
}

pub trait EmbeddedContent: Element {}

pub trait FlowContent: Element {
    // This method give user the total control of the node content
    fn update_websys_node_signal<S, T, F>(self, signal: S, updater: F) -> Self
    where
        S: signals::signal::Signal<Item = T> + 'static,
        T: Clone + 'static,
        F: Fn(T, &web_sys::Element) + 'static,
    {
        let ws_element = self.websys_element().clone();
        spawn_for_each(signal, move |value| {
            updater(value, &ws_element);
        });
        self
    }
}

impl<T> TextContent for T where T: FlowContent {}

pub trait FormContent: Element {}
pub trait FormLabelableContent: Element {}
pub trait FormListedContent: Element {}
pub trait FormResettableContent: Element {}
pub trait FormSubmittableContent: Element {}

pub trait HeadingContent: Element {}
pub trait InteractiveContent: Element {}
pub trait MetadataContent: Element {}
pub trait PalpableContent: Element {}
pub trait PhrasingContent: Element {}
pub trait SectioningContent: Element {}
pub trait TransparentContent: Element {}

pub trait TraitA: Element {
    fn href(self, value: &str) -> Self {
        self.websys_element()
            .set_attribute("href", value)
            .expect_throw("self.websys_element().set_attribute(\"href\", value)");
        self
    }
}

pub trait TraitCol: Element {}

pub trait TraitInput: Element {
    #[deprecated(since = "0.0.0", note = "Use a label instead of a placeholder")]
    fn placeholder(self, text: &str) -> Self {
        self.websys_element()
            .set_attribute("placeholder", text)
            .expect_throw("self.websys_element().set_attribute(\"placeholder\", text)");
        self
    }
    fn autofocus(self) -> Self {
        self.websys_element()
            .set_attribute("autofocus", "")
            .expect_throw("self.websys_element().set_attribute(\"autofocus\", \"\")");
        self
    }
    fn r#type(self, input_type: InputType) -> Self {
        self.websys_element()
            .set_attribute("type", input_type.as_str())
            .expect_throw("self.websys_element().set_attribute(\"type\", input_type.as_str())");
        self
    }
    fn value(self, value: &str) -> Self {
        self.websys_element()
            .unchecked_ref::<web_sys::HtmlInputElement>()
            .set_value(value);
        self
    }
    fn value_signal<S, T>(self, signal: S) -> Self
    where
        T: Clone + ToString + 'static,
        S: signals::signal::Signal<Item = T> + 'static,
    {
        let ws_input: web_sys::HtmlInputElement = self.websys_element().clone().unchecked_into();
        spawn_for_each(signal, move |value| {
            ws_input.set_value(&value.to_string());
        });
        self
    }
    fn checked(self, value: bool) -> Self {
        self.websys_element()
            .unchecked_ref::<web_sys::HtmlInputElement>()
            .set_checked(value);
        self
    }
    fn checked_signal<S>(self, signal: S) -> Self
    where
        S: signals::signal::Signal<Item = bool> + 'static,
    {
        let ws_input: web_sys::HtmlInputElement = self.websys_element().clone().unchecked_into();
        spawn_for_each(signal, move |value| {
            ws_input.set_checked(value);
        });
        self
    }
}

pub trait TraitLabel: Element {
    fn r#for(self, element_id: &str) -> Self {
        self.websys_element()
            .set_attribute("for", element_id)
            .expect_throw("self.websys_element().set_attribute(\"for\", element_id)");
        self
    }

    fn form(self, element_id: &str) -> Self {
        self.websys_element()
            .set_attribute("form", element_id)
            .expect_throw("self.websys_element().set_attribute(\"form\", element_id)");
        self
    }
}

pub trait TraitLi: Element {}

pub trait TraitFigure: Element {
    // TODO
    fn caption_not_implement_yet(self) -> Self {
        self
    }
}

pub trait TraitOption: Element {}
pub trait TraitTr: Element {}
pub trait TraitTextArea: Element {
    // TODO
    fn text(self) -> Self {
        self
    }
    fn text_signal(self) -> Self {
        self
    }
}

pub trait FlowNonInteractiveContent: Element {}
pub trait PhrasingNonInteractiveContent: Element {}
pub trait Headings: Element {}

/// Permitted content of `<a>`: Transparent, containing either flow content (excluding interactive content) or phrasing content.
/// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a
// How about:
//  * PhrasingContent: TextArea and other elements which are PhrasingContent but not FlowContent?
pub trait ChildOfA {}
impl<T> ChildOfA for T where T: FlowNonInteractiveContent {}

pub trait ChildOfAudioVideo {}

pub trait ChildOfDataList {}
impl<T> ChildOfDataList for T where T: PhrasingContent {}

pub trait ChildOfDetails {}
impl<T> ChildOfDetails for T where T: FlowContent {}

pub trait ChildOfDl {}

pub trait ChildOfFieldSet {}
impl<T> ChildOfFieldSet for T where T: FlowContent {}

pub trait ChildOfFigure {}
impl<T> ChildOfFigure for T where T: FlowContent {}


pub trait ChildOfPicture {}

pub trait ChildOfRtc {}
impl<T> ChildOfRtc for T where T: PhrasingContent {}

pub trait ChildOfSelect {}

pub trait ChildOfSummary {}
//impl<T> ChildOfSummary for T where T: HeadingContent {} 
impl<T> ChildOfSummary for T where T: PhrasingContent {}

pub trait ChildOfTable {}
pub trait ChildOfTr {}