Adding in signal_vec::unsync::mutable, and also support for using SignalVec with dominator
This commit is contained in:
parent
a7a29befd2
commit
d99dbbbee8
|
@ -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", {
|
||||
|
|
|
@ -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<A, F>(self, compare: F) -> SortBy<Self, F>
|
||||
fn sort_by<F>(self, compare: F) -> SortBy<Self, F>
|
||||
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<F, U>(self, callback: F) -> ForEach<SignalVecStream<Self>, F, U>
|
||||
where F: FnMut(VecChange<Self::Item>) -> U,
|
||||
// TODO allow for errors ?
|
||||
U: IntoFuture<Item = (), Error = ()>,
|
||||
Self:Sized {
|
||||
|
||||
self.to_stream().for_each(callback)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn by_ref(&mut self) -> &mut Self {
|
||||
self
|
||||
|
@ -516,6 +529,112 @@ impl<A, F> SignalVec for SortBy<A, F>
|
|||
}
|
||||
|
||||
|
||||
// TODO verify that this is correct
|
||||
pub mod unsync {
|
||||
use super::{SignalVec, VecChange};
|
||||
use futures::unsync::mpsc;
|
||||
use futures::{Async, Stream};
|
||||
|
||||
|
||||
pub struct Sender<A> {
|
||||
values: Vec<A>,
|
||||
sender: mpsc::UnboundedSender<VecChange<A>>,
|
||||
}
|
||||
|
||||
impl<A: Clone> Sender<A> {
|
||||
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<A> Sender<A> {
|
||||
pub fn pop(&mut self) -> Option<A> {
|
||||
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<A> {
|
||||
receiver: mpsc::UnboundedReceiver<VecChange<A>>,
|
||||
}
|
||||
|
||||
// TODO have it send a Replace at the beginning
|
||||
impl<A> SignalVec for Receiver<A> {
|
||||
type Item = A;
|
||||
|
||||
#[inline]
|
||||
fn poll(&mut self) -> Async<Option<VecChange<Self::Item>>> {
|
||||
self.receiver.poll().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
pub fn mutable<A>() -> (Sender<A>, Receiver<A>) {
|
||||
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};
|
||||
|
|
|
@ -10,6 +10,30 @@ pub fn create_element_ns<A: IElement>(name: &str, namespace: &str) -> A
|
|||
js!( return document.createElementNS(@{namespace}, @{name}); ).try_into().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn insert_at<A: INode, B: INode>(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<A: INode, B: INode>(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<A: INode>(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) {
|
||||
|
|
|
@ -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<A, B>(signal: A, mut callback: B) -> CancelableFutureHandle
|
|||
}
|
||||
|
||||
|
||||
fn for_each_vec<A, B>(signal: A, mut callback: B) -> CancelableFutureHandle
|
||||
where A: SignalVec + 'static,
|
||||
B: FnMut(VecChange<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<A>(element: &TextNode, callbacks: &mut Callbacks, signal: A)
|
||||
where A: Signal<Item = String> + 'static {
|
||||
|
@ -166,6 +187,7 @@ pub fn set_focused_bool<A: IHtmlElement + Clone + 'static>(element: &A, callback
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
// TODO inline this ?
|
||||
pub fn insert_children_signal<A, B, C>(element: &A, callbacks: &mut Callbacks, signal: C)
|
||||
where A: INode + Clone + 'static,
|
||||
|
@ -191,7 +213,7 @@ pub fn insert_children_signal<A, B, C>(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<Item = &'a mut Dom>>(element: &A, callbacks: &mut Callbacks, value: B) {
|
||||
|
@ -202,3 +224,120 @@ pub fn insert_children_iter<'a, A: INode, B: IntoIterator<Item = &'a mut Dom>>(e
|
|||
element.append_child(&dom.element);
|
||||
}
|
||||
}
|
||||
|
||||
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>>,
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -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<A: AsRef<Reference>>(self, element: &A, _callbacks: &mut Callbacks, name: &str) {
|
||||
|
@ -151,11 +159,18 @@ impl<A: Signal<Item = bool> + 'static> Focused for Dynamic<A> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<A, B> Children for Dynamic<B>
|
||||
/*impl<A, B> Children for Dynamic<B>
|
||||
where A: IntoIterator<Item = Dom>,
|
||||
B: Signal<Item = A> + 'static {
|
||||
#[inline]
|
||||
fn insert_children<C: INode + Clone + 'static>(self, element: &C, callbacks: &mut Callbacks) {
|
||||
operations::insert_children_signal(element, callbacks, self.0)
|
||||
}
|
||||
}*/
|
||||
|
||||
impl<A> Children for Dynamic<A> where A: SignalVec<Item = Dom> + 'static {
|
||||
#[inline]
|
||||
fn insert_children<C: INode + Clone + 'static>(self, element: &C, callbacks: &mut Callbacks) {
|
||||
operations::insert_children_signal_vec(element, callbacks, self.0)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue