silkenweb_dom/
element_list.rs

1//! Manage reactive lists of DOM elements.
2use std::{
3    cell::{Ref, RefCell},
4    collections::{BTreeMap, BTreeSet},
5    mem,
6    ops::Bound::{Excluded, Unbounded},
7    rc::Rc,
8};
9
10use silkenweb_reactive::{
11    clone,
12    signal::{ReadSignal, Signal},
13};
14use web_sys as dom;
15
16use crate::{DomElement, Element, ElementBuilder};
17
18/// A filterable, ordered element list.
19///
20/// This owns the data to create child elements, and will manage adding/removing
21/// them from the DOM as the filter requires.
22///
23/// The list is ordered by `Key`.
24pub struct ElementList<Key, Value> {
25    visible_items: Rc<RefCell<OrderedElementList<Key>>>,
26    generate_child: Rc<dyn Fn(&Value) -> Element>,
27    items: BTreeMap<Key, StoredItem<Value>>,
28    filter: Box<dyn Fn(&Value) -> ReadSignal<bool>>,
29}
30
31impl<Key, Value> ElementList<Key, Value>
32where
33    Key: 'static + Clone + Ord + Eq,
34    Value: 'static,
35{
36    /// Create a new [`ElementList`].
37    ///
38    /// # Panic
39    ///
40    /// Panics if `root` has already had children added to it.
41    pub fn new<GenerateChild, ChildElem, ParentElem>(
42        root: ParentElem,
43        generate_child: GenerateChild,
44        initial: impl Iterator<Item = (Key, Value)>,
45    ) -> Self
46    where
47        ChildElem: Into<Element>,
48        ParentElem: Into<ElementBuilder>,
49        GenerateChild: 'static + Fn(&Value) -> ChildElem,
50    {
51        let mut new = Self {
52            visible_items: Rc::new(RefCell::new(OrderedElementList::new(root.into()))),
53            generate_child: Rc::new(move |c| generate_child(c).into()),
54            items: BTreeMap::new(),
55            filter: Box::new(|_| Signal::new(true).read()),
56        };
57
58        for (key, elem) in initial {
59            new.insert(key, elem);
60        }
61
62        new
63    }
64
65    /// `true` iff the list, without a filter applied, is empty.
66    pub fn is_empty(&self) -> bool {
67        self.items.is_empty()
68    }
69
70    /// The length of the list without any filter applied.
71    pub fn len(&self) -> usize {
72        self.items.len()
73    }
74
75    /// Insert an item into the list. If an item exists at `key`, it is
76    /// replaced.
77    pub fn insert(&mut self, key: Key, item: Value) {
78        let item = Rc::new(RefCell::new(item));
79        let updater = self.updater(&key, &item);
80
81        self.items.insert(key, StoredItem { item, updater });
82    }
83
84    /// Pop the last element from the list. If the list is empty, this has no
85    /// effect.
86    pub fn pop(&mut self) {
87        if let Some((key, _)) = self.items.iter().next_back() {
88            // RUSTC(btree_pop_last): Don't clone the key and just pop last
89            clone!(key);
90            self.items.remove(&key);
91            self.visible_items.borrow_mut().remove(&key);
92        }
93    }
94
95    /// Remove the item corresponding to `key`. If the item is not in the list,
96    /// this has no effect.
97    pub fn remove(&mut self, key: &Key) {
98        if self.items.remove(key).is_some() {
99            self.visible_items.borrow_mut().remove(key)
100        }
101    }
102
103    /// Apply a filter to the list, replacing any existing filter.
104    pub fn filter(&mut self, f: impl 'static + Fn(&Value) -> ReadSignal<bool>) {
105        let old_items = mem::take(&mut self.items);
106        self.filter = Box::new(f);
107
108        for (key, StoredItem { item, updater }) in old_items {
109            mem::drop(updater);
110            let updater = self.updater(&key, &item);
111            self.items.insert(key, StoredItem { item, updater });
112        }
113    }
114
115    /// Remove all items for which `f` returns `false`. Matching items that are
116    /// currently filtered out will still be removed.
117    pub fn retain(&mut self, f: impl Fn(&Value) -> bool) {
118        // RUSTC(btree_map_retain): Use retain
119        let mut to_remove = BTreeSet::new();
120
121        for (key, value) in &self.items {
122            if !f(&value.item.borrow()) {
123                to_remove.insert(key.clone());
124            }
125        }
126
127        for key in to_remove {
128            self.remove(&key);
129        }
130    }
131
132    /// An iterator over all values in the list, including hidden items. If
133    /// `Value` is interiorly mutable and reactivity with the filter
134    /// is correctly set up, it's safe to mutate the items.
135    pub fn values(&mut self) -> impl Iterator<Item = Ref<Value>> {
136        self.items.values_mut().map(|stored| stored.item.borrow())
137    }
138
139    /// Clear all the items from the list, including filtered items.
140    pub fn clear(&mut self) {
141        self.visible_items.borrow_mut().clear();
142        self.items.clear();
143    }
144
145    fn updater(&self, key: &Key, item: &Rc<RefCell<Value>>) -> ReadSignal<()> {
146        (self.filter)(&item.borrow()).map({
147            let storage = self.visible_items.clone();
148            clone!(item, key);
149            let generate_child = self.generate_child.clone();
150
151            move |&visible| {
152                if visible {
153                    storage
154                        .borrow_mut()
155                        .insert(key.clone(), generate_child(&item.borrow()));
156                } else {
157                    storage.borrow_mut().remove(&key);
158                }
159            }
160        })
161    }
162}
163
164impl<Key, T> DomElement for ElementList<Key, T> {
165    type Target = dom::Element;
166
167    fn dom_element(&self) -> Self::Target {
168        self.visible_items.borrow().dom_element()
169    }
170}
171
172/// A list ordered by `Key`.
173pub struct OrderedElementList<Key> {
174    root: ElementBuilder,
175    items: BTreeMap<Key, Element>,
176}
177
178impl<Key> OrderedElementList<Key>
179where
180    Key: Ord + Eq,
181{
182    /// Create a new [`OrderedElementList`].
183    ///
184    /// # Panic
185    ///
186    /// Panics if `root` has already had children added to it.
187    pub fn new<ParentElem>(root: ParentElem) -> Self
188    where
189        ParentElem: Into<ElementBuilder>,
190    {
191        let root = root.into();
192        assert!(root.element.children.is_empty());
193
194        Self {
195            root,
196            items: BTreeMap::new(),
197        }
198    }
199
200    pub fn is_empty(&self) -> bool {
201        self.items.is_empty()
202    }
203
204    pub fn len(&self) -> usize {
205        self.items.len()
206    }
207
208    /// Insert an element. If the element exists, it will be replaced.
209    pub fn insert(&mut self, key: Key, element: Element) {
210        // TODO(testing): Add a test to make sure a reactive element gives us the
211        // correct dom_element.
212        let dom_element = element.dom_element();
213
214        if let Some((_key, next_elem)) = self.items.range((Excluded(&key), Unbounded)).next() {
215            self.root
216                .insert_child_before(&dom_element, &next_elem.dom_element());
217        } else {
218            self.root.append_child(&dom_element);
219        }
220
221        if let Some(existing_elem) = self.items.insert(key, element) {
222            self.root.remove_child(&existing_elem.dom_element());
223        }
224    }
225
226    /// Remove an item from the list. If no item exists for `key`, this has no
227    /// effect.
228    pub fn remove(&mut self, key: &Key) {
229        if let Some(element) = self.items.remove(key) {
230            self.root.remove_child(&element.dom_element());
231        }
232    }
233
234    /// Clear the list.
235    pub fn clear(&mut self) {
236        for element in self.items.values() {
237            self.root.remove_child(&element.dom_element());
238        }
239
240        self.items.clear();
241    }
242}
243
244impl<Key> DomElement for OrderedElementList<Key> {
245    type Target = dom::Element;
246
247    fn dom_element(&self) -> Self::Target {
248        self.root.dom_element()
249    }
250}
251
252struct StoredItem<T> {
253    item: SharedItem<T>,
254    updater: ReadSignal<()>,
255}
256
257type SharedItem<T> = Rc<RefCell<T>>;