webelements 0.1.1

A macro that transforms html to rust code
Documentation
use std::str::FromStr;

use elem::ElemTy;
use wasm_bindgen::{prelude::Closure, JsCast};
use web_sys::InputEvent;

use crate::{Error, Result};

pub use web_sys::MouseEvent;

pub mod elem {
    use wasm_bindgen::JsCast;
    use we_derive::element_types;
    pub trait ElemTy {
        type Elem: AsRef<web_sys::Element>;
        fn make() -> crate::Result<Self::Elem>;
    }
    element_types!();
}

pub trait WebElementBuilder {
    type Elem: ElemTy;
    fn build() -> Result<Self>
    where
        Self: std::marker::Sized;
}

pub trait WebElement: WebElementBuilder {
    fn init(&mut self) -> Result<()>;
}

#[derive(Debug, Clone)]
pub struct Element<E>
where
    E: ElemTy,
{
    element: E::Elem,
}

impl<E> AsRef<Element<E>> for Element<E>
where
    E: ElemTy,
{
    fn as_ref(&self) -> &Element<E> {
        self.root()
    }
}

impl<E> Element<E>
where
    E: ElemTy,
{
    pub fn new() -> Result<Element<E>> {
        let element = E::make()?;
        Ok(Self { element })
    }

    pub fn from_element(element: E::Elem) -> Self {
        Self { element }
    }

    fn as_element(&self) -> &web_sys::Element {
        self.element.as_ref()
    }

    fn as_node(&self) -> &web_sys::Node {
        self.element.as_ref()
    }

    pub fn append<T: ElemTy>(&self, other: impl AsRef<Element<T>>) -> Result<()> {
        self.element
            .as_ref()
            .append_child(other.as_ref().as_node())?;
        Ok(())
    }

    pub fn append_list<T: ElemTy>(
        &self,
        items: impl IntoIterator<Item = impl AsRef<Element<T>>>,
    ) -> Result<()> {
        items.into_iter().try_for_each(|i| self.append(i))
    }

    pub fn root(&self) -> &Element<E> {
        self
    }

    pub fn has_class(&self, class: impl AsRef<str>) -> bool {
        let class_string: String = self.as_element().class_name();
        for class_name in class_string.split_whitespace() {
            if class.as_ref() == class_name {
                return true;
            }
        }
        false
    }

    pub fn toggle_class(&self, class: impl AsRef<str>) {
        for class in class.as_ref().split_whitespace() {
            if self.has_class(class) {
                self.remove_class(class);
            } else {
                self.add_class(class);
            }
        }
    }

    pub fn add_class(&self, class: impl AsRef<str>) {
        for class in class.as_ref().split_whitespace() {
            if !self.has_class(class) {
                let mut class_string: String = self.as_element().class_name();
                class_string.push_str(&format!(" {}", class));
                self.as_element().set_class_name(class_string.trim());
            }
        }
    }

    pub fn set_class(&self, class: impl AsRef<str>) {
        self.as_element().set_class_name(class.as_ref());
    }

    pub fn clear_class(&self) {
        self.as_element().set_class_name("");
    }

    pub fn remove_class(&self, class: impl AsRef<str>) {
        for class in class.as_ref().split_whitespace() {
            if self.has_class(class) {
                let class_string = self.as_element().class_name();
                let mut new_string = Vec::<&str>::new();
                for class_name in class_string.split_whitespace() {
                    if class_name != class {
                        new_string.push(class_name)
                    }
                }
                let new_string = new_string.join(" ");
                self.as_element().set_class_name(new_string.trim());
            }
        }
    }

    pub fn set_text(&self, text: impl AsRef<str>) {
        self.as_element().set_inner_html(text.as_ref())
    }

    pub fn set_attr(&self, name: impl AsRef<str>, value: impl AsRef<str>) -> Result<()> {
        self.as_element()
            .set_attribute(name.as_ref(), value.as_ref())?;
        Ok(())
    }

    pub fn del_attr(&self, name: impl AsRef<str>) -> Result<()> {
        self.as_element().remove_attribute(name.as_ref())?;
        Ok(())
    }

    pub fn attr(&self, name: impl AsRef<str>) -> Option<String> {
        self.as_element().get_attribute(name.as_ref())
    }

    pub fn on_click(&self, callback: impl FnMut(MouseEvent) + 'static) -> Result<()> {
        let closure = Closure::wrap(Box::new(callback) as Box<dyn FnMut(MouseEvent)>);
        self.as_element()
            .add_event_listener_with_callback("click", closure.as_ref().unchecked_ref())
            .map_err(Error::JsError)?;
        closure.forget();
        Ok(())
    }
}

impl Element<elem::Base> {
    pub fn style(&self) -> web_sys::CssStyleDeclaration {
        self.element.style()
    }
}

impl Element<elem::Button> {}

impl Element<elem::Input> {
    pub fn on_input(&self, callback: impl FnMut(InputEvent) + 'static) -> Result<()> {
        let closure = Closure::wrap(Box::new(callback) as Box<dyn FnMut(InputEvent)>);
        self.as_element()
            .add_event_listener_with_callback("input", closure.as_ref().unchecked_ref())
            .map_err(Error::JsError)?;
        closure.forget();
        Ok(())
    }

    pub fn set_min<T: ToString>(&self, value: T) {
        self.element.set_min(&value.to_string())
    }

    pub fn set_max<T: ToString>(&self, value: T) {
        self.element.set_max(&value.to_string())
    }

    pub fn set_value<T: ToString>(&self, value: T) {
        self.element.set_value(&value.to_string())
    }

    pub fn get_value<T: FromStr>(&self) -> Result<T> {
        self.element.value().parse::<T>().map_err(|_| Error::Value)
    }
}