use stdweb::PromiseFuture; use discard::{Discard, DiscardOnDrop}; use stdweb::Reference; use stdweb::web::TextNode; use signals::{Signal, cancelable_future, CancelableFutureHandle}; use dom_operations; use dom::{Dom, IStyle}; use callbacks::Callbacks; use std::iter::IntoIterator; use stdweb::traits::{INode, IElement, IHtmlElement}; #[inline] fn for_each(signal: A, mut callback: B) -> CancelableFutureHandle where A: Signal + 'static, B: FnMut(A::Item) + 'static { let future = signal.for_each(move |value| { callback(value); Ok(()) }); // TODO make this more efficient ? let (handle, future) = cancelable_future(future, |_| ()); PromiseFuture::spawn(future); DiscardOnDrop::leak(handle) } // TODO inline this ? pub fn set_text_signal(element: &TextNode, callbacks: &mut Callbacks, signal: A) where A: Signal + 'static { let element = element.clone(); let handle = for_each(signal, move |value| { dom_operations::set_text(&element, &value); }); callbacks.after_remove(move || handle.discard()); } // TODO inline this ? pub fn set_property_signal<'a, A, B>(element: &A, callbacks: &mut Callbacks, name: &str, signal: B) where A: AsRef + Clone + 'static, B: Signal> + 'static { let element = element.clone(); let name = name.to_owned(); let handle = for_each(signal, move |value| { dom_operations::set_property(&element, &name, value.as_ref().map(|x| x.as_str())); }); callbacks.after_remove(move || handle.discard()); } #[inline] pub fn set_property_str>(element: &A, name: &str, value: &str) { dom_operations::set_property(element, name, Some(value)) } // TODO inline this ? pub fn set_attribute_signal(element: &A, callbacks: &mut Callbacks, name: &str, signal: B, namespace: Option<&str>) where A: IElement + Clone + 'static, B: Signal> + '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), } }); callbacks.after_remove(move || handle.discard()); } #[inline] pub fn set_attribute_str(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(element: &A, callbacks: &mut Callbacks, name: &str, signal: B) where A: IElement + Clone + 'static, B: Signal + 'static { let element = element.clone(); let name = name.to_owned(); let handle = for_each(signal, move |value| { dom_operations::toggle_class(&element, &name, value); }); callbacks.after_remove(move || handle.discard()); } #[inline] pub fn toggle_class_bool(element: &A, name: &str, value: bool) { dom_operations::toggle_class(element, name, value) } // TODO inline this ? pub fn set_style_signal(element: &A, callbacks: &mut Callbacks, name: &str, signal: B, important: bool) where A: IStyle + Clone + 'static, B: Signal> + '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), } }); callbacks.after_remove(move || handle.discard()); } #[inline] pub fn set_style_str(element: &A, name: &str, value: &str, important: bool) { element.set_style(name, value, important) } // TODO inline this ? pub fn set_focused_signal(element: &A, callbacks: &mut Callbacks, signal: B) where A: IHtmlElement + Clone + 'static, B: Signal + '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 callbacks.after_remove(move || handle.discard()); }); } #[inline] pub fn set_focused_bool(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); }); } // TODO inline this ? pub fn insert_children_signal(element: &A, callbacks: &mut Callbacks, signal: C) where A: INode + Clone + 'static, B: IntoIterator, C: Signal + 'static { let element = element.clone(); let mut old_children: Vec = 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` callbacks.after_remove(move || handle.discard()); } #[inline] pub fn insert_children_iter<'a, A: INode, B: IntoIterator>(element: &A, callbacks: &mut Callbacks, value: B) { 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); } }