use crate::{SilexError, SilexResult};
use silex_core::reactivity::{Effect, NodeId, batch, create_scope, dispose};
use silex_core::traits::RxRead;
use silex_dom::prelude::View;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use web_sys::Node;
pub trait ForLoopSource {
type Item: Clone;
fn as_slice(&self) -> SilexResult<&[Self::Item]>;
}
impl<T: Clone> ForLoopSource for Vec<T> {
type Item = T;
fn as_slice(&self) -> SilexResult<&[T]> {
Ok(self.as_slice())
}
}
impl<T: Clone> ForLoopSource for Option<Vec<T>> {
type Item = T;
fn as_slice(&self) -> SilexResult<&[T]> {
match self {
Some(v) => Ok(v.as_slice()),
None => Ok(&[]),
}
}
}
impl<T: Clone> ForLoopSource for SilexResult<Vec<T>> {
type Item = T;
fn as_slice(&self) -> SilexResult<&[T]> {
match self {
Ok(v) => Ok(v.as_slice()),
Err(e) => Err(e.clone()),
}
}
}
pub trait LoopKey<Item> {
type Key: std::hash::Hash + Eq + Clone + 'static;
fn get_key(&self, item: &Item) -> Self::Key;
}
impl<F, Item, K> LoopKey<Item> for F
where
F: Fn(&Item) -> K,
K: std::hash::Hash + Eq + Clone + 'static,
{
type Key = K;
fn get_key(&self, item: &Item) -> Self::Key {
(self)(item)
}
}
pub trait LoopMap<Item> {
type View: View;
fn map(&self, item: Item) -> Self::View;
}
impl<F, Item, V> LoopMap<Item> for F
where
F: Fn(Item) -> V,
V: View,
{
type View = V;
fn map(&self, item: Item) -> Self::View {
(self)(item)
}
}
pub struct For<ItemsFn, KeyFn, MapFn> {
items: Rc<ItemsFn>,
key: Rc<KeyFn>,
map: Rc<MapFn>,
}
impl<ItemsFn, KeyFn, MapFn> Clone for For<ItemsFn, KeyFn, MapFn> {
fn clone(&self) -> Self {
Self {
items: self.items.clone(),
key: self.key.clone(),
map: self.map.clone(),
}
}
}
impl<ItemsFn, KeyFn, MapFn> For<ItemsFn, KeyFn, MapFn> {
pub fn new<Item, Key, V>(items: ItemsFn, key: KeyFn, map: MapFn) -> Self
where
ItemsFn: RxRead,
ItemsFn::Value: ForLoopSource<Item = Item>,
KeyFn: Fn(&Item) -> Key,
MapFn: Fn(Item) -> V,
{
Self {
items: Rc::new(items),
key: Rc::new(key),
map: Rc::new(map),
}
}
}
impl<ItemsFn, KeyFn, MapFn> View for For<ItemsFn, KeyFn, MapFn>
where
ItemsFn: RxRead + 'static,
ItemsFn::Value: ForLoopSource + 'static,
KeyFn: LoopKey<<ItemsFn::Value as ForLoopSource>::Item> + 'static,
MapFn: LoopMap<<ItemsFn::Value as ForLoopSource>::Item> + 'static,
<ItemsFn::Value as ForLoopSource>::Item: 'static,
{
fn mount(self, parent: &Node, attrs: Vec<silex_dom::attribute::PendingAttribute>) {
self.mount_internal(parent, attrs);
}
fn mount_ref(&self, parent: &Node, attrs: Vec<silex_dom::attribute::PendingAttribute>) {
self.clone().mount_internal(parent, attrs);
}
}
impl<ItemsFn, KeyFn, MapFn> For<ItemsFn, KeyFn, MapFn>
where
ItemsFn: RxRead + 'static,
ItemsFn::Value: ForLoopSource + 'static,
KeyFn: LoopKey<<ItemsFn::Value as ForLoopSource>::Item> + 'static,
MapFn: LoopMap<<ItemsFn::Value as ForLoopSource>::Item> + 'static,
<ItemsFn::Value as ForLoopSource>::Item: 'static,
{
fn mount_internal(self, parent: &Node, _attrs: Vec<silex_dom::attribute::PendingAttribute>) {
let document = silex_dom::document();
let start_marker = document.create_comment("for-start");
let start_node: Node = start_marker.into();
if let Err(e) = parent.append_child(&start_node).map_err(SilexError::from) {
silex_core::error::handle_error(e);
return;
}
let end_marker = document.create_comment("for-end");
let end_node: Node = end_marker.into();
if let Err(e) = parent.append_child(&end_node).map_err(SilexError::from) {
silex_core::error::handle_error(e);
return;
}
let items_fn = self.items;
let key_fn = self.key;
let map_fn = self.map;
let active_rows = Rc::new(RefCell::new(HashMap::<
<KeyFn as LoopKey<<ItemsFn::Value as ForLoopSource>::Item>>::Key,
(Vec<Node>, NodeId),
>::new()));
Effect::new(move |_| {
let mut rows_map = active_rows.borrow_mut();
items_fn.with(|items| {
let items_slice = match items.as_slice() {
Ok(s) => s,
Err(e) => {
silex_core::error::handle_error(e);
return;
}
};
batch(|| {
let mut new_keys = HashSet::new();
let mut new_rows_order = Vec::with_capacity(items_slice.len());
for item_ref in items_slice {
let key = key_fn.get_key(item_ref);
new_keys.insert(key.clone());
if let Some((nodes, id)) = rows_map.get(&key) {
new_rows_order.push((key, nodes.clone(), *id, None));
} else {
let item_owned = item_ref.clone();
let (nodes, scope_id, fragment) =
silex_core::reactivity::untrack(|| {
let fragment = document.create_document_fragment();
let fragment_node: Node = fragment.clone().into();
let map_fn = map_fn.clone();
let scope_id = create_scope(move || {
let view = map_fn.map(item_owned);
view.mount(&fragment_node, Vec::new());
});
let nodes_list = fragment.child_nodes();
let len = nodes_list.length();
let mut nodes = Vec::with_capacity(len as usize);
for i in 0..len {
if let Some(n) = nodes_list.item(i) {
nodes.push(n);
}
}
(nodes, scope_id, fragment)
});
new_rows_order.push((key, nodes, scope_id, Some(fragment)));
};
}
rows_map.retain(|k, (nodes, id)| {
if !new_keys.contains(k) {
for node in nodes {
if let Some(p) = node.parent_node() {
let _ = p.remove_child(node);
}
}
dispose(*id);
false
} else {
true
}
});
let mut cursor = start_node.next_sibling();
for (key, nodes, id, fragment_opt) in new_rows_order {
if let Some(frag) = fragment_opt {
let effective_cursor = cursor.as_ref().unwrap_or(&end_node);
if let Some(parent) = effective_cursor.parent_node() {
let _ = parent.insert_before(&frag, Some(effective_cursor));
}
rows_map.insert(key, (nodes, id));
} else {
if nodes.is_empty() {
rows_map.insert(key, (nodes, id));
continue;
}
let first_node = &nodes[0];
let is_in_place = if let Some(ref c) = cursor {
c.is_same_node(Some(first_node))
} else {
false
};
if is_in_place {
for _ in 0..nodes.len() {
cursor = cursor.and_then(|c| c.next_sibling());
}
} else {
let effective_cursor = cursor.as_ref().unwrap_or(&end_node);
if let Some(parent) = effective_cursor.parent_node() {
for node in &nodes {
let _ = parent.insert_before(node, Some(effective_cursor));
}
}
}
rows_map.insert(key, (nodes, id));
}
}
});
});
});
}
}