diff --git a/examples/todomvc/src/main.rs b/examples/todomvc/src/main.rs index 525de9c..af20f18 100644 --- a/examples/todomvc/src/main.rs +++ b/examples/todomvc/src/main.rs @@ -16,7 +16,9 @@ use stdweb::web::event::ClickEvent; use stdweb::web::IParentNode; use signals::signal; +use signals::signal_vec; use signals::signal::Signal; +use signals::signal_vec::SignalVec; use dominator::traits::*; use dominator::{Dom, text}; @@ -36,7 +38,7 @@ fn main() { let mut count = 0; - let (sender_elements, receiver_elements) = signal::unsync::mutable(count); + let (sender_count, receiver_count) = signal::unsync::mutable(count); let mut width: u32 = 10; @@ -46,6 +48,8 @@ fn main() { let (sender3, receiver3) = signal::unsync::mutable(vec![width]); let (text_sender, text_receiver) = signal::unsync::mutable(format!("{}", width)); + let (mut sender_elements, receiver_elements) = signal_vec::unsync::mutable(); + /*let style_width = receiver1.switch(move |x| { receiver2.clone().switch(move |y| { receiver3.clone().map(move |z| { @@ -62,6 +66,22 @@ fn main() { }; + let mut elements_index = 0; + + let mut increment = move || { + elements_index += 1; + elements_index + }; + + sender_elements.push((increment(), 1)); + sender_elements.push((increment(), 2)); + sender_elements.push((increment(), 3)); + sender_elements.push((increment(), 4)); + sender_elements.push((increment(), 5)); + sender_elements.push((increment(), 6)); + sender_elements.push((increment(), 7)); + + dominator::append_dom(&document().query_selector("body").unwrap().unwrap(), html!("div", { style("border", "10px solid blue"); @@ -70,6 +90,8 @@ fn main() { text(text_receiver.dynamic()), + text(receiver_count.map(|x| format!(" - {}", x)).dynamic()), + html!("div", { style("width", style_width.dynamic()); style("height", "50px"); @@ -84,30 +106,36 @@ fn main() { sender2.set(vec![width]).unwrap(); sender3.set(vec![width]).unwrap(); text_sender.set(format!("{}", width)).unwrap(); - sender_elements.set(count).unwrap(); + sender_count.set(count).unwrap(); + sender_elements.push((increment(), 8)); + sender_elements.push((increment(), 0)); + sender_elements.push((increment(), 5)); + sender_elements.push((increment(), 9)); }); - children(receiver_elements.map(|count| { - (0..count).map(|_| { - html!("div", { - style("border", "5px solid red"); - style("width", "50px"); - style("height", "50px"); + children( + receiver_elements + .filter_map(|(x, y)| { + if y > 2 { + Some((x, y + 100)) + } else { + None + } }) - }) - }).dynamic()); - }), - - html!("div", { - style("width", "50px"); - style("height", "50px"); - style("background-color", "red"); - children(&mut [ - html!("div", { - style("width", "10px"); - style("height", "10px"); - style("background-color", "orange"); - }) - ]); + .sort_by(|&(_, a), &(_, b)| { + a.cmp(&b).reverse() + }) + .map(|(x, y)| { + html!("div", { + style("border", "5px solid red"); + style("width", "100px"); + style("height", "50px"); + children(&mut [ + text(format!("({}, {})", x, y)) + ]); + }) + }) + .dynamic() + ); }), html!("div", { diff --git a/signals/src/signal_vec.rs b/signals/src/signal_vec.rs index 82391a5..9ddb465 100644 --- a/signals/src/signal_vec.rs +++ b/signals/src/signal_vec.rs @@ -1,5 +1,7 @@ use std::cmp::Ordering; use futures::{Stream, Poll, Async}; +use futures::stream::ForEach; +use futures::future::IntoFuture; #[derive(Debug, Clone, PartialEq, Eq)] @@ -82,7 +84,7 @@ pub trait SignalVec { } #[inline] - fn sort_by(self, compare: F) -> SortBy + fn sort_by(self, compare: F) -> SortBy where F: FnMut(&Self::Item, &Self::Item) -> Ordering, Self: Sized { SortBy { @@ -101,6 +103,17 @@ pub trait SignalVec { } } + #[inline] + // TODO file Rust bug about bad error message when `callback` isn't marked as `mut` + fn for_each(self, callback: F) -> ForEach, F, U> + where F: FnMut(VecChange) -> U, + // TODO allow for errors ? + U: IntoFuture, + Self:Sized { + + self.to_stream().for_each(callback) + } + #[inline] fn by_ref(&mut self) -> &mut Self { self @@ -516,6 +529,112 @@ impl SignalVec for SortBy } +// TODO verify that this is correct +pub mod unsync { + use super::{SignalVec, VecChange}; + use futures::unsync::mpsc; + use futures::{Async, Stream}; + + + pub struct Sender { + values: Vec, + sender: mpsc::UnboundedSender>, + } + + impl Sender { + pub fn push(&mut self, value: A) { + let clone = value.clone(); + self.values.push(value); + self.sender.unbounded_send(VecChange::Push { value: clone }).unwrap(); + } + + pub fn insert(&mut self, index: usize, value: A) { + let clone = value.clone(); + + if index == self.values.len() { + self.values.push(value); + self.sender.unbounded_send(VecChange::Push { value: clone }).unwrap(); + + } else { + self.values.insert(index, value); + self.sender.unbounded_send(VecChange::InsertAt { index, value: clone }).unwrap(); + } + } + + // TODO replace this with something else, like entry or IndexMut or whatever + pub fn update(&mut self, index: usize, value: A) { + let clone = value.clone(); + self.values[index] = value; + self.sender.unbounded_send(VecChange::UpdateAt { index, value: clone }).unwrap(); + } + } + + impl Sender { + pub fn pop(&mut self) -> Option { + let value = self.values.pop(); + + if let Some(_) = value { + self.sender.unbounded_send(VecChange::Pop {}).unwrap(); + } + + value + } + + pub fn remove(&mut self, index: usize) -> A { + let len = self.values.len(); + + let value = self.values.remove(index); + + if index == (len - 1) { + self.sender.unbounded_send(VecChange::Pop {}).unwrap(); + + } else { + self.sender.unbounded_send(VecChange::RemoveAt { index }).unwrap(); + } + + value + } + + pub fn clear(&mut self) { + let len = self.values.len(); + + self.values.clear(); + + if len > 0 { + self.sender.unbounded_send(VecChange::Clear {}).unwrap(); + } + } + } + + + pub struct Receiver { + receiver: mpsc::UnboundedReceiver>, + } + + // TODO have it send a Replace at the beginning + impl SignalVec for Receiver { + type Item = A; + + #[inline] + fn poll(&mut self) -> Async>> { + self.receiver.poll().unwrap() + } + } + + + #[inline] + pub fn mutable() -> (Sender, Receiver) { + let (sender, receiver) = mpsc::unbounded(); + + let sender = Sender { values: vec![], sender }; + + let receiver = Receiver { receiver }; + + (sender, receiver) + } +} + + #[cfg(test)] mod tests { use futures::{Future, Poll, task}; diff --git a/src/dom_operations.rs b/src/dom_operations.rs index 9f88ffe..2ed9bc4 100644 --- a/src/dom_operations.rs +++ b/src/dom_operations.rs @@ -10,6 +10,30 @@ pub fn create_element_ns(name: &str, namespace: &str) -> A js!( return document.createElementNS(@{namespace}, @{name}); ).try_into().unwrap() } +#[inline] +pub fn insert_at(parent: &A, index: u32, child: &B) { + js! { @(no_return) + var parent = @{parent.as_ref()}; + parent.insertBefore(@{child.as_ref()}, parent.childNodes[@{index}]); + } +} + +#[inline] +pub fn update_at(parent: &A, index: u32, child: &B) { + js! { @(no_return) + var parent = @{parent.as_ref()}; + parent.replaceChild(@{child.as_ref()}, parent.childNodes[@{index}]); + } +} + +#[inline] +pub fn remove_at(parent: &A, index: u32) { + js! { @(no_return) + var parent = @{parent.as_ref()}; + parent.removeChild(parent.childNodes[@{index}]); + } +} + // TODO this should be in stdweb #[inline] pub fn set_text(element: &TextNode, value: &str) { diff --git a/src/operations.rs b/src/operations.rs index 4e8c5ee..c736ec0 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -1,8 +1,11 @@ +use std::rc::Rc; +use std::cell::{Cell, RefCell}; use stdweb::PromiseFuture; use discard::{Discard, DiscardOnDrop}; use stdweb::Reference; use stdweb::web::TextNode; use signals::signal::{Signal, cancelable_future, CancelableFutureHandle}; +use signals::signal_vec::{VecChange, SignalVec}; use dom_operations; use dom::{Dom, IStyle}; use callbacks::Callbacks; @@ -29,6 +32,24 @@ fn for_each(signal: A, mut callback: B) -> CancelableFutureHandle } +fn for_each_vec(signal: A, mut callback: B) -> CancelableFutureHandle + where A: SignalVec + 'static, + B: FnMut(VecChange) + '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 { @@ -166,6 +187,7 @@ pub fn set_focused_bool(element: &A, callback } +/* // TODO inline this ? pub fn insert_children_signal(element: &A, callbacks: &mut Callbacks, signal: C) where A: INode + Clone + 'static, @@ -191,7 +213,7 @@ pub fn insert_children_signal(element: &A, callbacks: &mut Callbacks, s // 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) { @@ -202,3 +224,120 @@ pub fn insert_children_iter<'a, A: INode, B: IntoIterator>(e element.append_child(&dom.element); } } + +pub fn insert_children_signal_vec(element: &A, callbacks: &mut Callbacks, signal: B) + where A: INode + Clone + 'static, + B: SignalVec + 'static { + + let element = element.clone(); + + // TODO does this create a new struct type every time ? + struct State { + is_inserted: Cell, + children: RefCell>, + } + + 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(); + + // TODO figure out a better way to iterate + for mut dom in &mut children[..] { + 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(); + + *children = values; + + // TODO use document fragment ? + // TODO figure out a better way to iterate + for dom in &mut children[..] { + element.append_child(&dom.element); + + if state.is_inserted.get() { + 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); + + 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); + }, + + VecChange::UpdateAt { index, mut value } => { + // TODO better usize -> u32 conversion + dom_operations::update_at(&element, index as u32, &value.element); + + 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()[index] = value; + }, + + VecChange::RemoveAt { index } => { + // TODO better usize -> u32 conversion + dom_operations::remove_at(&element, index as u32); + + state.children.borrow_mut().remove(index); + }, + + VecChange::Push { mut value } => { + element.append_child(&value.element); + + 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().push(value); + }, + + VecChange::Pop {} => { + let mut children = state.children.borrow_mut(); + + let index = children.len() - 1; + + // TODO better usize -> u32 conversion + dom_operations::remove_at(&element, index as u32); + + children.pop(); + }, + + VecChange::Clear {} => { + dom_operations::remove_all_children(&element); + + *state.children.borrow_mut() = vec![]; + }, + } + }); + + // TODO verify that this will drop `old_children` + callbacks.after_remove(move || handle.discard()); +} diff --git a/src/traits.rs b/src/traits.rs index 1b5bf89..a5eb648 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,6 +5,7 @@ use stdweb::unstable::TryInto; use dom::{Dom, IStyle, Dynamic}; use callbacks::Callbacks; use signals::signal::Signal; +use signals::signal_vec::SignalVec; use operations; @@ -56,6 +57,13 @@ impl<'a> Text for &'a str { } } +impl<'a> Text for String { + #[inline] + fn into_dom(self) -> Dom { + self.as_str().into_dom() + } +} + impl<'a> Property for &'a str { #[inline] fn set_property>(self, element: &A, _callbacks: &mut Callbacks, name: &str) { @@ -151,11 +159,18 @@ impl + 'static> Focused for Dynamic { } } -impl Children for Dynamic +/*impl Children for Dynamic where A: IntoIterator, B: Signal + 'static { #[inline] fn insert_children(self, element: &C, callbacks: &mut Callbacks) { operations::insert_children_signal(element, callbacks, self.0) } +}*/ + +impl Children for Dynamic where A: SignalVec + 'static { + #[inline] + fn insert_children(self, element: &C, callbacks: &mut Callbacks) { + operations::insert_children_signal_vec(element, callbacks, self.0) + } }