silex 0.1.0-beta.1

Next Generation High-Performance Rust Web Framework based on fine-grained reactivity and no-virtual-DOM architecture.
Documentation
use crate::SilexError;
use crate::flow::for_loop::ForLoopSource;
use silex_core::reactivity::{
    Effect, NodeId, ReadSignal, WriteSignal, batch, create_scope, dispose, signal,
};
use silex_core::traits::{IntoSignal, Set, With};
use silex_dom::View;
use std::cell::RefCell;
use std::rc::Rc;
use web_sys::Node;

/// Index 组件:类似于 For,但基于索引(Index)进行迭代。
///
/// 当列表顺序发生变化时,DOM 节点不会移动,只是对应的数据 Signal 会更新。
/// 适用于基础类型列表或无唯一 Key 的列表。
#[derive(Clone)]
pub struct Index<ItemsFn, Item, Items, MapFn, V> {
    items: Rc<ItemsFn>,
    map: Rc<MapFn>,
    _marker: std::marker::PhantomData<(Item, Items, V)>,
}

impl<ItemsFn, Item, Items, MapFn, V> Index<ItemsFn, Item, Items, MapFn, V>
where
    ItemsFn: With<Value = Items> + 'static,
    Items: ForLoopSource<Item = Item> + 'static,
    MapFn: Fn(ReadSignal<Item>, usize) -> V + 'static,
    V: View,
    Item: 'static,
{
    pub fn new(items: impl IntoSignal<Value = Items, Signal = ItemsFn>, map: MapFn) -> Self {
        Self {
            items: Rc::new(items.into_signal()),
            map: Rc::new(map),
            _marker: std::marker::PhantomData,
        }
    }
}

// Helper struct for row state
struct IndexRow<Item> {
    // setter to update the signal
    setter: WriteSignal<Item>,
    scope_id: NodeId,
    // Store nodes for removal
    nodes: Vec<Node>,
}

impl<ItemsFn, Item, Items, MapFn, V> View for Index<ItemsFn, Item, Items, MapFn, V>
where
    ItemsFn: With<Value = Items> + 'static,
    Items: ForLoopSource<Item = Item> + 'static,
    MapFn: Fn(ReadSignal<Item>, usize) -> V + 'static,
    V: View,
    Item: Clone + 'static, // Item needs clone for Signal updates
{
    fn mount(self, parent: &Node) {
        let document = silex_dom::document();
        let start_marker = document.create_comment("index-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("index-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 rows = Rc::new(RefCell::new(Vec::<IndexRow<Item>>::new()));
        let items_fn = self.items;
        let map_fn = self.map;

        Effect::new(move |_| {
            items_fn.with(|items| {
                let items_slice = match items.as_slice() {
                    Ok(s) => s,
                    Err(e) => {
                        silex_core::error::handle_error(e);
                        return;
                    }
                };

                let mut rows_lock = rows.borrow_mut();

                batch(|| {
                    let new_len = items_slice.len();
                    let old_len = rows_lock.len();
                    let common_len = std::cmp::min(new_len, old_len);

                    // 1. Update existing rows
                    for (i, item) in items_slice.iter().take(common_len).enumerate() {
                        rows_lock[i].setter.set(item.clone());
                    }

                    // 2. Add new rows
                    if new_len > old_len {
                        // We need access to map_fn inside the loop.
                        // map_fn is Rc, so cheap to clone.
                        // But strictly speaking we only need it inside create_scope.

                        // Optimization: Pre-clone common variables if possible, or just clone map_fn inside loop.
                        // `map` is Rc<MapFn>.

                        for (i, item) in items_slice[common_len..].iter().enumerate() {
                            let real_index = common_len + i;
                            // Create signal for the row
                            let (get, set) = signal(item.clone());

                            let fragment = document.create_document_fragment();
                            let fragment_node: Node = fragment.clone().into();
                            let fragment_node_clone = fragment_node.clone();

                            let map_fn = map_fn.clone();

                            let scope_id = create_scope(move || {
                                (map_fn)(get, real_index).mount(&fragment_node_clone);
                            });

                            let nodes_list = fragment.child_nodes();
                            let len = nodes_list.length();
                            let mut nodes = Vec::with_capacity(len as usize);
                            for j in 0..len {
                                if let Some(n) = nodes_list.item(j) {
                                    nodes.push(n);
                                }
                            }

                            if let Some(p) = end_node.parent_node() {
                                let _ = p.insert_before(&fragment_node, Some(&end_node)); // Pass fragment itself
                            }

                            rows_lock.push(IndexRow {
                                setter: set,
                                scope_id,
                                nodes,
                            });
                        }
                    }

                    // 3. Remove extra rows
                    if old_len > new_len {
                        let to_remove = rows_lock.split_off(new_len);
                        for row in to_remove {
                            dispose(row.scope_id);
                            for node in row.nodes {
                                if let Some(p) = node.parent_node() {
                                    let _ = p.remove_child(&node);
                                }
                            }
                        }
                    }
                });
            });
        });
    }
}