#[cfg(feature = "queue-render")]
use std::{cell::Cell, rc::Rc};
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use super::{AChildNode, AttributeValueList, ElementTag, ElementType, Nodes};
#[derive(Debug)]
pub struct Element {
ws_element: WsElement,
attributes: AttributeValueList,
nodes: Nodes,
#[cfg(feature = "queue-render")]
unmounted: Rc<Cell<bool>>,
}
#[cfg(feature = "queue-render")]
impl Drop for Element {
fn drop(&mut self) {
self.unmounted.set(true);
}
}
impl Clone for Element {
fn clone(&self) -> Self {
let ws_element = self.ws_element.shadow_clone();
let nodes = self.nodes.clone();
nodes.append_to(ws_element.ws_node());
Self {
ws_element,
nodes,
attributes: self.attributes.clone(),
#[cfg(feature = "queue-render")]
unmounted: Rc::new(Cell::new(false)),
}
}
}
impl AChildNode for Element {
fn ws_node(&self) -> &web_sys::Node {
self.ws_element.ws_node()
}
}
impl Element {
pub fn new_ns<E: ElementTag>(tag: E) -> Self {
Self {
ws_element: WsElement::new(E::NAMESPACE, tag.tag_name()),
attributes: Default::default(),
nodes: Default::default(),
#[cfg(feature = "queue-render")]
unmounted: Rc::new(Cell::new(false)),
}
}
pub fn from_ws_element(ws_element: web_sys::Element) -> Self {
Self {
ws_element: WsElement {
element_type: ws_element.tag_name().to_ascii_lowercase().as_str().into(),
ws_element,
},
attributes: Default::default(),
nodes: Default::default(),
#[cfg(feature = "queue-render")]
unmounted: Rc::new(Cell::new(false)),
}
}
#[cfg(feature = "queue-render")]
pub fn unmounted(&self) -> Rc<Cell<bool>> {
self.unmounted.clone()
}
pub fn is_empty(&self) -> bool {
self.nodes.count() == 0
}
pub fn ws_element(&self) -> &WsElement {
&self.ws_element
}
pub fn ws_node_and_nodes_mut(&mut self) -> (&web_sys::Node, &mut Nodes) {
(self.ws_element.as_ref(), &mut self.nodes)
}
pub fn ws_html_element(&self) -> &web_sys::HtmlElement {
self.ws_element.ws_element.unchecked_ref()
}
pub fn element_type(&self) -> ElementType {
self.ws_element.element_type
}
pub fn attribute_list_mut(&mut self) -> &mut AttributeValueList {
&mut self.attributes
}
#[cfg(test)]
pub fn nodes(&self) -> &Nodes {
&self.nodes
}
pub fn nodes_mut(&mut self) -> &mut Nodes {
&mut self.nodes
}
}
#[derive(Debug, Clone)]
pub struct WsElement {
ws_element: web_sys::Element,
element_type: ElementType,
}
pub trait AttributeValueAsString {
fn to_string(self) -> String;
}
macro_rules! impl_string_attribute {
($($TypeName:ident)+) => {
$(
impl AttributeValueAsString for $TypeName {
fn to_string(self) -> String {
ToString::to_string(&self)
}
}
)+
};
}
impl_string_attribute! { i32 u32 f64 }
impl WsElement {
pub fn new(namespace: &str, tag: &str) -> Self {
Self {
ws_element: crate::utils::document()
.create_element_ns(Some(namespace), tag)
.expect_throw("dom::element::WsElement::new"),
element_type: tag.into(),
}
}
pub fn into_inner(self) -> web_sys::Element {
self.ws_element
}
pub fn as_ref(&self) -> &web_sys::Element {
&self.ws_element
}
pub fn ws_node(&self) -> &web_sys::Node {
self.ws_element.as_ref()
}
pub fn ws_event_target(&self) -> &web_sys::EventTarget {
self.ws_element.as_ref()
}
pub fn unchecked_ref<T: JsCast>(&self) -> &T {
self.ws_element.unchecked_ref::<T>()
}
pub fn html_element(&self) -> &web_sys::HtmlElement {
self.ws_element.unchecked_ref()
}
pub fn unchecked_into<T: JsCast>(&self) -> T {
self.ws_element.clone().unchecked_into::<T>()
}
fn shadow_clone(&self) -> Self {
Self {
ws_element: self
.ws_element
.clone_node_with_deep(false)
.expect_throw("render::element::WsElement::clone")
.unchecked_into(),
element_type: self.element_type,
}
}
pub fn set_id(&self, id: &str) {
self.ws_element.set_id(id);
}
pub fn set_text_content(&self, text: Option<&str>) {
self.ws_element.set_text_content(text);
}
pub fn set_str_attribute(&self, attribute_name: &str, attribute_value: &str) {
self.ws_element
.set_attribute(attribute_name, attribute_value)
.expect_throw("dom::element::WsElement::set_str_attribute");
}
pub fn remove_attribute(&self, attribute_name: &str) {
self.ws_element
.remove_attribute(attribute_name)
.expect_throw("dom::element::WsElement::remove_attribute");
}
pub fn set_attribute<T: AttributeValueAsString>(
&self,
attribute_name: &str,
attribute_value: T,
) {
self.set_str_attribute(attribute_name, &attribute_value.to_string());
}
pub fn set_bool_attribute(&self, name: &str, value: bool) {
if value {
self.set_str_attribute(name, "");
} else {
self.remove_attribute(name);
}
}
pub fn add_class(&self, class_name: &str) {
self.ws_element
.class_list()
.add_1(class_name)
.expect_throw("dom::element::WsElement::add_class");
}
pub fn remove_class(&self, class_name: &str) {
self.ws_element
.class_list()
.remove_1(class_name)
.expect_throw("dom::element::WsElement::remove_class");
}
pub fn add_class_optional(&self, class_name: Option<&str>) {
if let Some(class_name) = class_name {
self.add_class(class_name);
}
}
pub fn remove_class_optional(&self, class_name: Option<&str>) {
if let Some(class_name) = class_name {
self.remove_class(class_name);
}
}
#[must_use = "Make sure that the return value is handled if queue_render = false"]
pub fn set_value(&self, value: &str, queue_render: bool) -> bool {
match self.element_type {
ElementType::Input => {
let input = self.ws_element.unchecked_ref::<web_sys::HtmlInputElement>();
input.set_value(value);
}
ElementType::Select => {
if queue_render {
let select = self
.ws_element
.unchecked_ref::<web_sys::HtmlSelectElement>();
select.set_value(value);
}
return true;
}
ElementType::TextArea => {
let text_area = self
.ws_element
.unchecked_ref::<web_sys::HtmlTextAreaElement>();
text_area.set_value(value);
}
ElementType::Option => {
let option = self
.ws_element
.unchecked_ref::<web_sys::HtmlOptionElement>();
option.set_value(value);
}
ElementType::Other => {
log::warn!(
".value() is called on an element that is not <input>, <select>, <option>, <textarea>"
);
}
}
false
}
#[allow(clippy::ptr_arg)]
pub fn set_value_for_qr(&self, value: &String) {
let _user = self.set_value(value, true);
}
pub fn set_value_for_qr_optional(&self, value: &Option<String>) {
match value {
Some(value) => {
let _user = self.set_value(value, true);
}
None => self.set_selected_index(-1),
}
}
pub fn set_selected_index(&self, index: i32) {
match self.element_type {
ElementType::Select => {
let select = self
.ws_element
.unchecked_ref::<web_sys::HtmlSelectElement>();
select.set_selected_index(index);
}
_ => {
log::warn!(".set_selected_index() is called on an element that is not a <select>");
}
}
}
pub fn set_selected_index_ref(&self, index: &usize) {
self.set_selected_index(*index as i32);
}
pub fn set_selected_index_optional(&self, index: &Option<usize>) {
match index {
Some(index) => self.set_selected_index(*index as i32),
None => self.set_selected_index(-1),
}
}
pub fn checked_ref(&self, value: &bool) {
self.checked(*value);
}
pub fn checked(&self, value: bool) {
if self.element_type == ElementType::Input {
let input = self.ws_element.unchecked_ref::<web_sys::HtmlInputElement>();
input.set_checked(value);
} else {
log::warn!(".checked() is called on an element that is not an <input>");
}
}
pub fn enabled_ref(&self, value: &bool) {
self.enabled(*value);
}
pub fn enabled(&self, value: bool) {
self.set_bool_attribute("disabled", !value);
}
pub fn focus_ref(&self, value: &bool) {
self.focus(*value);
}
pub fn focus(&self, value: bool) {
if value {
self.html_element()
.focus()
.expect_throw("render::base::element::ElementUpdater::focus");
}
}
pub fn scroll_to_view_with_bool(&self, align_to_top: bool) {
self.ws_element.scroll_into_view_with_bool(align_to_top);
}
pub fn scroll_to_view_with_options(&self, options: &web_sys::ScrollIntoViewOptions) {
self.ws_element
.scroll_into_view_with_scroll_into_view_options(options);
}
}