2018-03-01 08:39:58 -05:00
|
|
|
use std::rc::Rc;
|
|
|
|
use std::cell::{Cell, RefCell};
|
2018-02-25 06:58:20 -05:00
|
|
|
use stdweb::PromiseFuture;
|
|
|
|
use discard::{Discard, DiscardOnDrop};
|
2018-03-08 17:07:17 -05:00
|
|
|
use stdweb::{Reference, JsSerialize};
|
2018-02-25 06:58:20 -05:00
|
|
|
use stdweb::web::TextNode;
|
2018-02-28 06:59:45 -05:00
|
|
|
use signals::signal::{Signal, cancelable_future, CancelableFutureHandle};
|
2018-03-01 08:39:58 -05:00
|
|
|
use signals::signal_vec::{VecChange, SignalVec};
|
2018-02-25 06:58:20 -05:00
|
|
|
use dom_operations;
|
|
|
|
use dom::{Dom, IStyle};
|
|
|
|
use callbacks::Callbacks;
|
|
|
|
use std::iter::IntoIterator;
|
|
|
|
use stdweb::traits::{INode, IElement, IHtmlElement};
|
2018-03-15 06:54:18 -04:00
|
|
|
use futures::future::Future;
|
2018-02-25 06:58:20 -05:00
|
|
|
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
// TODO this should probably be in stdweb
|
2018-02-25 06:58:20 -05:00
|
|
|
#[inline]
|
2018-03-15 06:54:18 -04:00
|
|
|
pub fn spawn_future<F>(future: F) -> CancelableFutureHandle
|
|
|
|
where F: Future<Item = (), Error = ()> + 'static {
|
2018-02-25 06:58:20 -05:00
|
|
|
// TODO make this more efficient ?
|
|
|
|
let (handle, future) = cancelable_future(future, |_| ());
|
|
|
|
|
|
|
|
PromiseFuture::spawn(future);
|
|
|
|
|
|
|
|
DiscardOnDrop::leak(handle)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
#[inline]
|
|
|
|
fn for_each<A, B>(signal: A, mut callback: B) -> CancelableFutureHandle
|
|
|
|
where A: Signal + 'static,
|
|
|
|
B: FnMut(A::Item) + 'static {
|
2018-03-01 08:39:58 -05:00
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
spawn_future(signal.for_each(move |value| {
|
2018-03-01 08:39:58 -05:00
|
|
|
callback(value);
|
|
|
|
Ok(())
|
2018-03-15 06:54:18 -04:00
|
|
|
}))
|
|
|
|
}
|
2018-03-01 08:39:58 -05:00
|
|
|
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
#[inline]
|
|
|
|
fn for_each_vec<A, B>(signal: A, mut callback: B) -> CancelableFutureHandle
|
|
|
|
where A: SignalVec + 'static,
|
|
|
|
B: FnMut(VecChange<A::Item>) + 'static {
|
2018-03-01 08:39:58 -05:00
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
spawn_future(signal.for_each(move |value| {
|
|
|
|
callback(value);
|
|
|
|
Ok(())
|
|
|
|
}))
|
2018-03-01 08:39:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-02-25 06:58:20 -05:00
|
|
|
// TODO inline this ?
|
|
|
|
pub fn set_text_signal<A>(element: &TextNode, callbacks: &mut Callbacks, signal: A)
|
|
|
|
where A: Signal<Item = String> + 'static {
|
|
|
|
|
|
|
|
let element = element.clone();
|
|
|
|
|
|
|
|
let handle = for_each(signal, move |value| {
|
|
|
|
dom_operations::set_text(&element, &value);
|
|
|
|
});
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
callbacks.after_remove(handle);
|
2018-02-25 06:58:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO inline this ?
|
2018-03-08 17:07:17 -05:00
|
|
|
pub fn set_property_signal<'a, A, B, C>(element: &A, callbacks: &mut Callbacks, name: &str, signal: C)
|
2018-02-25 06:58:20 -05:00
|
|
|
where A: AsRef<Reference> + Clone + 'static,
|
2018-03-08 17:07:17 -05:00
|
|
|
B: JsSerialize,
|
|
|
|
C: Signal<Item = B> + 'static {
|
2018-02-25 06:58:20 -05:00
|
|
|
|
|
|
|
let element = element.clone();
|
|
|
|
let name = name.to_owned();
|
|
|
|
|
|
|
|
let handle = for_each(signal, move |value| {
|
2018-03-08 17:07:17 -05:00
|
|
|
dom_operations::set_property(&element, &name, &value);
|
2018-02-25 06:58:20 -05:00
|
|
|
});
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
callbacks.after_remove(handle);
|
2018-02-25 06:58:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2018-03-08 17:07:17 -05:00
|
|
|
pub fn set_property_str<A: AsRef<Reference>, B: JsSerialize>(element: &A, name: &str, value: &B) {
|
|
|
|
dom_operations::set_property(element, name, value)
|
2018-02-25 06:58:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO inline this ?
|
|
|
|
pub fn set_attribute_signal<A, B>(element: &A, callbacks: &mut Callbacks, name: &str, signal: B, namespace: Option<&str>)
|
|
|
|
where A: IElement + Clone + 'static,
|
|
|
|
B: Signal<Item = Option<String>> + 'static {
|
|
|
|
|
|
|
|
let element = element.clone();
|
|
|
|
let name = name.to_owned();
|
|
|
|
let namespace = namespace.map(|x| x.to_owned());
|
|
|
|
|
|
|
|
let handle = for_each(signal, move |value| {
|
|
|
|
// TODO figure out a way to avoid this
|
|
|
|
let namespace = namespace.as_ref().map(|x| x.as_str());
|
|
|
|
|
|
|
|
match value {
|
|
|
|
Some(value) => dom_operations::set_attribute(&element, &name, &value, namespace),
|
|
|
|
None => dom_operations::remove_attribute(&element, &name, namespace),
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
callbacks.after_remove(handle);
|
2018-02-25 06:58:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn set_attribute_str<A: IElement>(element: &A, name: &str, value: &str, namespace: Option<&str>) {
|
|
|
|
dom_operations::set_attribute(element, name, value, namespace)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO inline this ?
|
|
|
|
pub fn toggle_class_signal<A, B>(element: &A, callbacks: &mut Callbacks, name: &str, signal: B)
|
|
|
|
where A: IElement + Clone + 'static,
|
|
|
|
B: Signal<Item = bool> + 'static {
|
|
|
|
|
|
|
|
let element = element.clone();
|
|
|
|
let name = name.to_owned();
|
|
|
|
|
|
|
|
let handle = for_each(signal, move |value| {
|
|
|
|
dom_operations::toggle_class(&element, &name, value);
|
|
|
|
});
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
callbacks.after_remove(handle);
|
2018-02-25 06:58:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn toggle_class_bool<A: IElement>(element: &A, name: &str, value: bool) {
|
|
|
|
dom_operations::toggle_class(element, name, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO inline this ?
|
|
|
|
pub fn set_style_signal<A, B>(element: &A, callbacks: &mut Callbacks, name: &str, signal: B, important: bool)
|
|
|
|
where A: IStyle + Clone + 'static,
|
|
|
|
B: Signal<Item = Option<String>> + 'static {
|
|
|
|
|
|
|
|
let element = element.clone();
|
|
|
|
let name = name.to_owned();
|
|
|
|
|
|
|
|
let handle = for_each(signal, move |value| {
|
|
|
|
match value {
|
|
|
|
Some(value) => element.set_style(&name, &value, important),
|
|
|
|
None => element.set_style(&name, "", important),
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
callbacks.after_remove(handle);
|
2018-02-25 06:58:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn set_style_str<A: IStyle>(element: &A, name: &str, value: &str, important: bool) {
|
|
|
|
element.set_style(name, value, important)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO inline this ?
|
|
|
|
pub fn set_focused_signal<A, B>(element: &A, callbacks: &mut Callbacks, signal: B)
|
|
|
|
where A: IHtmlElement + Clone + 'static,
|
|
|
|
B: Signal<Item = bool> + 'static {
|
|
|
|
|
|
|
|
let element = element.clone();
|
|
|
|
|
|
|
|
// This needs to use `after_insert` because calling `.focus()` on an element before it is in the DOM has no effect
|
|
|
|
callbacks.after_insert(move |callbacks| {
|
|
|
|
let handle = for_each(signal, move |value| {
|
|
|
|
dom_operations::set_focused(&element, value);
|
|
|
|
});
|
|
|
|
|
|
|
|
// TODO verify that this is correct under all circumstances
|
2018-03-15 06:54:18 -04:00
|
|
|
callbacks.after_remove(handle);
|
2018-02-25 06:58:20 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn set_focused_bool<A: IHtmlElement + Clone + 'static>(element: &A, callbacks: &mut Callbacks, value: bool) {
|
|
|
|
let element = element.clone();
|
|
|
|
|
|
|
|
// This needs to use `after_insert` because calling `.focus()` on an element before it is in the DOM has no effect
|
|
|
|
callbacks.after_insert(move |_| {
|
|
|
|
dom_operations::set_focused(&element, value);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-01 08:39:58 -05:00
|
|
|
/*
|
2018-02-25 06:58:20 -05:00
|
|
|
// TODO inline this ?
|
|
|
|
pub fn insert_children_signal<A, B, C>(element: &A, callbacks: &mut Callbacks, signal: C)
|
|
|
|
where A: INode + Clone + 'static,
|
|
|
|
B: IntoIterator<Item = Dom>,
|
|
|
|
C: Signal<Item = B> + 'static {
|
|
|
|
|
|
|
|
let element = element.clone();
|
|
|
|
|
|
|
|
let mut old_children: Vec<Dom> = vec![];
|
|
|
|
|
|
|
|
let handle = for_each(signal, move |value| {
|
|
|
|
dom_operations::remove_all_children(&element);
|
|
|
|
|
|
|
|
old_children = value.into_iter().map(|mut dom| {
|
|
|
|
element.append_child(&dom.element);
|
|
|
|
|
|
|
|
// TODO don't trigger this if the parent isn't inserted into the DOM
|
|
|
|
dom.callbacks.trigger_after_insert();
|
|
|
|
|
|
|
|
dom
|
|
|
|
}).collect();
|
|
|
|
});
|
|
|
|
|
|
|
|
// TODO verify that this will drop `old_children`
|
2018-03-15 06:54:18 -04:00
|
|
|
callbacks.after_remove(handle);
|
2018-03-01 08:39:58 -05:00
|
|
|
}*/
|
2018-02-25 06:58:20 -05:00
|
|
|
|
|
|
|
#[inline]
|
2018-02-26 03:01:04 -05:00
|
|
|
pub fn insert_children_iter<'a, A: INode, B: IntoIterator<Item = &'a mut Dom>>(element: &A, callbacks: &mut Callbacks, value: B) {
|
2018-02-25 06:58:20 -05:00
|
|
|
for dom in value.into_iter() {
|
|
|
|
callbacks.after_insert.append(&mut dom.callbacks.after_insert);
|
|
|
|
callbacks.after_remove.append(&mut dom.callbacks.after_remove);
|
|
|
|
|
|
|
|
element.append_child(&dom.element);
|
|
|
|
}
|
|
|
|
}
|
2018-03-01 08:39:58 -05:00
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
|
|
|
|
// TODO move this into the discard crate
|
|
|
|
// TODO verify that this is correct and doesn't leak memory or cause memory safety
|
|
|
|
pub struct RcDiscard<A>(*const A);
|
|
|
|
|
|
|
|
impl<A> RcDiscard<A> {
|
|
|
|
#[inline]
|
|
|
|
pub fn new(value: Rc<A>) -> Self {
|
|
|
|
RcDiscard(Rc::into_raw(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<A> Discard for RcDiscard<A> {
|
|
|
|
#[inline]
|
|
|
|
fn discard(self) {
|
|
|
|
unsafe {
|
|
|
|
Rc::from_raw(self.0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO move this into the discard crate
|
|
|
|
// TODO verify that this is correct and doesn't leak memory or cause memory safety
|
|
|
|
pub struct BoxDiscard<A>(*mut A);
|
|
|
|
|
|
|
|
impl<A> BoxDiscard<A> {
|
|
|
|
#[inline]
|
|
|
|
pub fn new(value: A) -> Self {
|
|
|
|
Self::from_box(Box::new(value))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn from_box(value: Box<A>) -> Self {
|
|
|
|
BoxDiscard(Box::into_raw(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<A> Discard for BoxDiscard<A> {
|
|
|
|
#[inline]
|
|
|
|
fn discard(self) {
|
|
|
|
unsafe {
|
|
|
|
Box::from_raw(self.0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-01 08:39:58 -05:00
|
|
|
pub fn insert_children_signal_vec<A, B>(element: &A, callbacks: &mut Callbacks, signal: B)
|
|
|
|
where A: INode + Clone + 'static,
|
|
|
|
B: SignalVec<Item = Dom> + 'static {
|
|
|
|
|
|
|
|
let element = element.clone();
|
|
|
|
|
|
|
|
// TODO does this create a new struct type every time ?
|
|
|
|
struct State {
|
|
|
|
is_inserted: Cell<bool>,
|
|
|
|
children: RefCell<Vec<Dom>>,
|
|
|
|
}
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
// TODO use two separate Rcs ?
|
2018-03-01 08:39:58 -05:00
|
|
|
let state = Rc::new(State {
|
|
|
|
is_inserted: Cell::new(false),
|
|
|
|
children: RefCell::new(vec![]),
|
|
|
|
});
|
|
|
|
|
|
|
|
{
|
|
|
|
let state = state.clone();
|
|
|
|
|
|
|
|
callbacks.after_insert(move |_| {
|
|
|
|
if !state.is_inserted.replace(true) {
|
|
|
|
let mut children = state.children.borrow_mut();
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
for dom in children.iter_mut() {
|
2018-03-01 08:39:58 -05:00
|
|
|
dom.callbacks.trigger_after_insert();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
let handle = for_each_vec(signal, move |change| {
|
|
|
|
match change {
|
|
|
|
VecChange::Replace { values } => {
|
|
|
|
dom_operations::remove_all_children(&element);
|
|
|
|
|
|
|
|
let mut children = state.children.borrow_mut();
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
for dom in children.drain(..) {
|
|
|
|
dom.callbacks.discard();
|
|
|
|
}
|
|
|
|
|
2018-03-01 08:39:58 -05:00
|
|
|
*children = values;
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
let is_inserted = state.is_inserted.get();
|
|
|
|
|
2018-03-01 08:39:58 -05:00
|
|
|
// TODO use document fragment ?
|
2018-03-15 06:54:18 -04:00
|
|
|
for dom in children.iter_mut() {
|
|
|
|
dom.callbacks.leak();
|
|
|
|
|
2018-03-01 08:39:58 -05:00
|
|
|
element.append_child(&dom.element);
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
if is_inserted {
|
2018-03-01 08:39:58 -05:00
|
|
|
dom.callbacks.trigger_after_insert();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
VecChange::InsertAt { index, mut value } => {
|
|
|
|
// TODO better usize -> u32 conversion
|
|
|
|
dom_operations::insert_at(&element, index as u32, &value.element);
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
value.callbacks.leak();
|
|
|
|
|
2018-03-01 08:39:58 -05:00
|
|
|
if state.is_inserted.get() {
|
|
|
|
value.callbacks.trigger_after_insert();
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO figure out a way to move this to the top
|
|
|
|
state.children.borrow_mut().insert(index, value);
|
|
|
|
},
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
VecChange::Push { mut value } => {
|
|
|
|
element.append_child(&value.element);
|
|
|
|
|
|
|
|
value.callbacks.leak();
|
2018-03-01 08:39:58 -05:00
|
|
|
|
|
|
|
if state.is_inserted.get() {
|
|
|
|
value.callbacks.trigger_after_insert();
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO figure out a way to move this to the top
|
2018-03-15 06:54:18 -04:00
|
|
|
state.children.borrow_mut().push(value);
|
2018-03-01 08:39:58 -05:00
|
|
|
},
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
VecChange::UpdateAt { index, mut value } => {
|
2018-03-01 08:39:58 -05:00
|
|
|
// TODO better usize -> u32 conversion
|
2018-03-15 06:54:18 -04:00
|
|
|
dom_operations::update_at(&element, index as u32, &value.element);
|
2018-03-01 08:39:58 -05:00
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
value.callbacks.leak();
|
2018-03-01 08:39:58 -05:00
|
|
|
|
|
|
|
if state.is_inserted.get() {
|
|
|
|
value.callbacks.trigger_after_insert();
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO figure out a way to move this to the top
|
2018-03-15 06:54:18 -04:00
|
|
|
let mut children = state.children.borrow_mut();
|
|
|
|
|
|
|
|
// TODO test this
|
|
|
|
::std::mem::swap(&mut children[index], &mut value);
|
|
|
|
|
|
|
|
value.callbacks.discard();
|
|
|
|
},
|
|
|
|
|
|
|
|
VecChange::RemoveAt { index } => {
|
|
|
|
// TODO better usize -> u32 conversion
|
|
|
|
dom_operations::remove_at(&element, index as u32);
|
|
|
|
|
|
|
|
state.children.borrow_mut().remove(index).callbacks.discard();
|
2018-03-01 08:39:58 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
VecChange::Pop {} => {
|
|
|
|
let mut children = state.children.borrow_mut();
|
|
|
|
|
|
|
|
let index = children.len() - 1;
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
// TODO create remove_last_child function ?
|
2018-03-01 08:39:58 -05:00
|
|
|
// TODO better usize -> u32 conversion
|
|
|
|
dom_operations::remove_at(&element, index as u32);
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
children.pop().unwrap().callbacks.discard();
|
2018-03-01 08:39:58 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
VecChange::Clear {} => {
|
|
|
|
dom_operations::remove_all_children(&element);
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
let mut children = state.children.borrow_mut();
|
|
|
|
|
|
|
|
for dom in children.drain(..) {
|
|
|
|
dom.callbacks.discard();
|
|
|
|
}
|
|
|
|
|
|
|
|
*children = vec![];
|
2018-03-01 08:39:58 -05:00
|
|
|
},
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-03-15 06:54:18 -04:00
|
|
|
// TODO verify that this will drop `children`
|
|
|
|
callbacks.after_remove(handle);
|
2018-03-01 08:39:58 -05:00
|
|
|
}
|