rust-dominator/src/dom.rs

386 lines
9.8 KiB
Rust
Raw Normal View History

2018-02-21 01:37:09 -05:00
use std;
use stdweb::{Reference, Value, ReferenceType};
use stdweb::unstable::{TryFrom, TryInto};
2018-03-18 14:49:40 -04:00
use stdweb::web::{IEventTarget, INode, IElement, IHtmlElement, HtmlElement, Node};
2018-02-21 01:37:09 -05:00
use stdweb::web::event::ConcreteEvent;
2018-02-25 06:58:20 -05:00
use callbacks::Callbacks;
use traits::*;
use dom_operations;
2018-03-15 06:54:18 -04:00
use operations::{BoxDiscard, spawn_future};
use futures::future::Future;
use discard::{Discard, DiscardOnDrop};
2018-02-25 06:58:20 -05:00
pub struct Dynamic<A>(pub(crate) A);
2018-02-21 01:37:09 -05:00
// TODO this should be in stdweb
pub trait IStyle: ReferenceType {
// TODO check that the style *actually* was changed
// TODO handle browser prefixes
#[inline]
fn set_style(&self, name: &str, value: &str, important: bool) {
if important {
js! { @(no_return)
@{self.as_ref()}.style.setProperty(@{name}, @{value}, "important");
}
} else {
js! { @(no_return)
@{self.as_ref()}.style.setProperty(@{name}, @{value}, "");
}
2018-02-21 01:37:09 -05:00
}
}
}
impl<A: IHtmlElement> IStyle for A {}
// TODO this should be in stdweb
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(instance_of = "CSSStyleRule")]
pub struct CssStyleRule(Reference);
impl IStyle for CssStyleRule {}
// https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#Valid%20Namespace%20URIs
pub const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml";
pub const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
2018-03-18 14:49:40 -04:00
// TODO this should be in stdweb
// TODO this should return HtmlBodyElement
pub fn body() -> HtmlElement {
js! ( return document.body; ).try_into().unwrap()
}
2018-03-15 06:54:18 -04:00
pub struct DomHandle {
parent: Node,
dom: Dom,
}
impl Discard for DomHandle {
#[inline]
fn discard(self) {
self.parent.remove_child(&self.dom.element).unwrap();
self.dom.callbacks.discard();
}
}
2018-02-25 06:58:20 -05:00
#[inline]
2018-03-15 06:54:18 -04:00
pub fn append_dom<A: INode>(parent: &A, mut dom: Dom) -> DomHandle {
2018-02-25 06:58:20 -05:00
parent.append_child(&dom.element);
2018-02-21 01:37:09 -05:00
2018-02-25 06:58:20 -05:00
dom.callbacks.trigger_after_insert();
2018-02-21 01:37:09 -05:00
2018-03-15 06:54:18 -04:00
// This prevents it from triggering after_remove
dom.callbacks.leak();
DomHandle {
parent: parent.as_node().clone(),
dom
}
2018-02-25 06:58:20 -05:00
}
2018-02-21 01:37:09 -05:00
2018-02-25 06:58:20 -05:00
#[inline]
pub fn text<A: Text>(value: A) -> Dom {
value.into_dom()
2018-02-21 01:37:09 -05:00
}
#[derive(Debug)]
2018-02-21 01:37:09 -05:00
pub struct Dom {
2018-02-25 06:58:20 -05:00
pub(crate) element: Node,
pub(crate) callbacks: Callbacks,
2018-02-21 01:37:09 -05:00
}
impl Dom {
#[inline]
2018-02-25 06:58:20 -05:00
pub(crate) fn new(element: Node) -> Self {
2018-02-21 01:37:09 -05:00
Self {
element,
callbacks: Callbacks::new(),
}
}
#[inline]
pub fn empty() -> Self {
// TODO is there a better way of doing this ?
Self::new(js!( return document.createComment(""); ).try_into().unwrap())
}
#[inline]
pub fn with_state<A, F>(mut state: A, initializer: F) -> Dom
where A: 'static,
F: FnOnce(&mut A) -> Dom {
let mut dom = initializer(&mut state);
2018-03-15 06:54:18 -04:00
// TODO make this more efficient somehow ? what if the value is already on the heap ?
dom.callbacks.after_remove(BoxDiscard::new(state));
2018-02-21 01:37:09 -05:00
dom
}
}
pub struct HtmlBuilder<A> {
element: A,
callbacks: Callbacks,
// TODO verify this with static types instead ?
has_children: bool,
}
2018-03-15 06:54:18 -04:00
impl<A> HtmlBuilder<A> {
#[inline]
pub fn future<F>(mut self, future: F) -> Self where F: Future<Item = (), Error = ()> + 'static {
self.callbacks.after_remove(DiscardOnDrop::leak(spawn_future(future)));
2018-03-15 06:54:18 -04:00
self
}
}
2018-02-21 01:37:09 -05:00
// TODO add in SVG nodes
impl<A: IElement> HtmlBuilder<A>
where <A as TryFrom<Value>>::Error: std::fmt::Debug {
#[inline]
pub fn new(name: &str) -> Self {
Self {
element: dom_operations::create_element_ns(name, HTML_NAMESPACE),
callbacks: Callbacks::new(),
has_children: false,
}
}
}
impl<A: AsRef<Reference> + Clone + 'static> HtmlBuilder<A> {
#[inline]
2018-02-25 06:58:20 -05:00
pub fn property<B: Property>(mut self, name: &str, value: B) -> Self {
value.set_property(&self.element, &mut self.callbacks, name);
2018-02-21 01:37:09 -05:00
self
}
}
2018-03-15 06:54:18 -04:00
struct EventListenerHandle {
event: &'static str,
element: Reference,
listener: Value,
}
impl Discard for EventListenerHandle {
#[inline]
fn discard(self) {
js! { @(no_return)
var listener = @{&self.listener};
@{&self.element}.removeEventListener(@{self.event}, listener);
listener.drop();
}
}
}
2018-02-21 01:37:09 -05:00
impl<A: IEventTarget> HtmlBuilder<A> {
// TODO maybe inline this ?
// TODO replace with element.add_event_listener
fn _event<T, F>(&mut self, listener: F)
where T: ConcreteEvent,
F: FnMut(T) + 'static {
2018-02-25 06:58:20 -05:00
let element = self.element.as_ref().clone();
2018-02-21 01:37:09 -05:00
let listener = js!(
var listener = @{listener};
@{&element}.addEventListener(@{T::EVENT_TYPE}, listener);
return listener;
);
2018-03-15 06:54:18 -04:00
self.callbacks.after_remove(EventListenerHandle {
event: T::EVENT_TYPE,
element,
listener,
2018-02-21 01:37:09 -05:00
});
}
#[inline]
pub fn event<T, F>(mut self, listener: F) -> Self
where T: ConcreteEvent,
F: FnMut(T) + 'static {
self._event(listener);
self
}
}
impl<A: INode + Clone + 'static> HtmlBuilder<A> {
#[inline]
2018-02-25 06:58:20 -05:00
pub fn children<B: Children>(mut self, children: B) -> Self {
2018-02-21 01:37:09 -05:00
assert_eq!(self.has_children, false);
self.has_children = true;
2018-02-25 06:58:20 -05:00
children.insert_children(&self.element, &mut self.callbacks);
2018-02-21 01:37:09 -05:00
self
}
}
impl<A: IElement + Clone + 'static> HtmlBuilder<A> {
#[inline]
2018-02-25 06:58:20 -05:00
pub fn attribute<B: Attribute>(mut self, name: &str, value: B) -> Self {
value.set_attribute(&self.element, &mut self.callbacks, name, None);
2018-02-21 01:37:09 -05:00
self
}
#[inline]
2018-02-25 06:58:20 -05:00
pub fn attribute_namespace<B: Attribute>(mut self, name: &str, value: B, namespace: &str) -> Self {
value.set_attribute(&self.element, &mut self.callbacks, name, Some(namespace));
2018-02-21 01:37:09 -05:00
self
}
#[inline]
2018-02-25 06:58:20 -05:00
pub fn class<B: Class>(mut self, name: &str, value: B) -> Self {
value.toggle_class(&self.element, &mut self.callbacks, name);
2018-02-21 01:37:09 -05:00
self
}
}
impl<A: IHtmlElement + Clone + 'static> HtmlBuilder<A> {
#[inline]
2018-02-25 06:58:20 -05:00
pub fn style<B: Style>(mut self, name: &str, value: B) -> Self {
value.set_style(&self.element, &mut self.callbacks, name, false);
2018-02-21 01:37:09 -05:00
self
}
#[inline]
2018-02-25 06:58:20 -05:00
pub fn style_important<B: Style>(mut self, name: &str, value: B) -> Self {
value.set_style(&self.element, &mut self.callbacks, name, true);
2018-02-21 01:37:09 -05:00
self
}
#[inline]
2018-02-25 06:58:20 -05:00
pub fn focused<B: Focused>(mut self, value: B) -> Self {
value.toggle_focused(&self.element, &mut self.callbacks);
2018-02-21 01:37:09 -05:00
self
}
}
impl<A: Into<Node>> From<HtmlBuilder<A>> for Dom {
#[inline]
fn from(dom: HtmlBuilder<A>) -> Self {
Self {
element: dom.element.into(),
callbacks: dom.callbacks,
}
}
}
2018-02-25 06:58:20 -05:00
// TODO better warning message for must_use
#[must_use]
2018-02-21 01:37:09 -05:00
pub struct StylesheetBuilder {
element: CssStyleRule,
callbacks: Callbacks,
}
2018-02-25 06:58:20 -05:00
// TODO remove the CssStyleRule when this is discarded
2018-02-21 01:37:09 -05:00
impl StylesheetBuilder {
#[inline]
pub fn new(selector: &str) -> Self {
lazy_static! {
// TODO better static type for this
static ref STYLESHEET: Reference = js!(
// TODO use createElementNS ?
var e = document.createElement("style");
e.type = "text/css";
document.head.appendChild(e);
return e.sheet;
).try_into().unwrap();
}
Self {
element: js!(
var stylesheet = @{&*STYLESHEET};
var length = stylesheet.cssRules.length;
stylesheet.insertRule(@{selector} + "{}", length);
return stylesheet.cssRules[length];
).try_into().unwrap(),
callbacks: Callbacks::new(),
}
}
#[inline]
2018-02-25 06:58:20 -05:00
pub fn style<B: Style>(mut self, name: &str, value: B) -> Self {
value.set_style(&self.element, &mut self.callbacks, name, false);
2018-02-21 01:37:09 -05:00
self
}
#[inline]
2018-02-25 06:58:20 -05:00
pub fn style_important<B: Style>(mut self, name: &str, value: B) -> Self {
value.set_style(&self.element, &mut self.callbacks, name, true);
2018-02-21 01:37:09 -05:00
self
}
// TODO return a Handle
#[inline]
pub fn done(mut self) {
self.callbacks.trigger_after_insert();
2018-03-15 06:54:18 -04:00
// This prevents it from triggering after_remove
self.callbacks.leak();
2018-02-21 01:37:09 -05:00
}
}
2018-02-25 06:58:20 -05:00
// TODO better warning message for must_use
#[must_use]
2018-02-21 01:37:09 -05:00
pub struct ClassBuilder {
stylesheet: StylesheetBuilder,
class_name: String,
}
impl ClassBuilder {
#[inline]
pub fn new() -> Self {
let class_name = {
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
// TODO replace this with a global counter in JavaScript ?
2018-02-21 01:37:09 -05:00
lazy_static! {
// TODO can this be made more efficient ?
// TODO use AtomicU32 instead ?
static ref CLASS_ID: AtomicUsize = ATOMIC_USIZE_INIT;
2018-02-21 01:37:09 -05:00
}
// TODO check for overflow ?
let id = CLASS_ID.fetch_add(1, Ordering::Relaxed);
2018-02-21 01:37:09 -05:00
// TODO make this more efficient ?
format!("__class_{}__", id)
};
Self {
// TODO make this more efficient ?
stylesheet: StylesheetBuilder::new(&format!(".{}", class_name)),
class_name,
}
}
#[inline]
2018-02-25 06:58:20 -05:00
pub fn style<B: Style>(mut self, name: &str, value: B) -> Self {
2018-02-21 01:37:09 -05:00
self.stylesheet = self.stylesheet.style(name, value);
self
}
#[inline]
2018-02-25 06:58:20 -05:00
pub fn style_important<B: Style>(mut self, name: &str, value: B) -> Self {
2018-02-21 01:37:09 -05:00
self.stylesheet = self.stylesheet.style_important(name, value);
self
}
// TODO return a Handle ?
#[inline]
pub fn done(self) -> String {
self.stylesheet.done();
self.class_name
}
}