patternfly_yew/components/table/model/
hook.rs

1use super::{StateModel, TableDataModel};
2use crate::prelude::{StateModelIter, TableModel};
3use std::collections::hash_map::Entry;
4use std::collections::HashMap;
5use std::fmt::{Debug, Formatter};
6use std::hash::Hash;
7use std::rc::Rc;
8use yew::html::IntoPropValue;
9use yew::prelude::*;
10
11pub type ExpansionCallback<K, C> = Callback<(K, ExpansionState<C>)>;
12
13#[derive(Clone, PartialEq, Eq)]
14pub enum ExpansionState<C>
15where
16    C: Clone + Eq,
17{
18    Row,
19    Column(C),
20}
21
22impl<C> Copy for ExpansionState<C> where C: Copy + Clone + Eq {}
23
24impl<C> Debug for ExpansionState<C>
25where
26    C: Clone + Eq,
27{
28    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
29        match self {
30            Self::Row => f.debug_tuple("ExpansionState::Row").finish(),
31            Self::Column(_) => f
32                .debug_tuple("ExpansionState::Column")
33                .field(&"..")
34                .finish(),
35        }
36    }
37}
38
39pub struct OnToggleCallback<C, M>(pub ExpansionCallback<M::Key, C>)
40where
41    C: Clone + Eq + 'static,
42    M: TableModel<C>;
43
44impl<C, M> Debug for OnToggleCallback<C, M>
45where
46    C: Clone + Eq + 'static,
47    M: TableModel<C>,
48{
49    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
50        f.debug_tuple("OnToggleCallback").field(&self.0).finish()
51    }
52}
53
54impl<C, M> PartialEq for OnToggleCallback<C, M>
55where
56    C: Clone + Eq + 'static,
57    M: TableModel<C>,
58{
59    fn eq(&self, other: &Self) -> bool {
60        self.0.eq(&other.0)
61    }
62}
63
64impl<C, M> Default for OnToggleCallback<C, M>
65where
66    C: Clone + Eq + 'static,
67    M: TableModel<C>,
68{
69    fn default() -> Self {
70        Self(Default::default())
71    }
72}
73
74impl<C, M> Clone for OnToggleCallback<C, M>
75where
76    C: Clone + Eq + 'static,
77    M: TableModel<C>,
78{
79    fn clone(&self) -> Self {
80        Self(self.0.clone())
81    }
82}
83
84impl<C, M> IntoPropValue<OnToggleCallback<C, M>> for ExpansionCallback<M::Key, C>
85where
86    C: Clone + Eq + 'static,
87    M: TableModel<C>,
88{
89    fn into_prop_value(self) -> OnToggleCallback<C, M> {
90        OnToggleCallback(self)
91    }
92}
93
94impl<C, M> IntoPropValue<OnToggleCallback<C, M>> for Rc<ExpansionCallback<M::Key, C>>
95where
96    C: Clone + Eq + 'static,
97    M: TableModel<C>,
98{
99    fn into_prop_value(self) -> OnToggleCallback<C, M> {
100        OnToggleCallback((*self).clone())
101    }
102}
103
104#[hook]
105pub fn use_table_data<C, M>(data: M) -> (UseTableData<C, M>, ExpansionCallback<M::Key, C>)
106where
107    C: Clone + Eq + 'static,
108    M: PartialEq + Clone + TableDataModel<C> + 'static,
109    M::Key: Hash,
110{
111    let state = use_mut_ref(HashMap::<M::Key, ExpansionState<C>>::new);
112    let model = {
113        let state = state.clone();
114        use_memo(data, move |model| {
115            state.borrow_mut().retain(|key, _| model.contains(key));
116            StateModel::new(model.clone(), state)
117        })
118    };
119
120    let trigger = use_force_update();
121
122    // FIXME: allow toggling entries without re-evaluating the whole table: https://github.com/patternfly-yew/patternfly-yew/issues/69
123    let ontoggle = Callback::from(move |(key, expansion_state)| {
124        match state.borrow_mut().entry(key) {
125            Entry::Vacant(entry) => {
126                entry.insert(expansion_state);
127            }
128            Entry::Occupied(mut entry) => {
129                if entry.get() != &expansion_state {
130                    entry.insert(expansion_state);
131                } else {
132                    entry.remove();
133                }
134            }
135        }
136
137        trigger.force_update();
138    });
139
140    ({ UseTableData { model } }, ontoggle)
141}
142
143pub struct UseTableData<C, M>
144where
145    C: Clone + Eq + 'static,
146    M: PartialEq + Clone + TableDataModel<C> + 'static,
147    M::Key: Hash,
148{
149    model: Rc<StateModel<C, M>>,
150}
151
152impl<C, M> TableModel<C> for UseTableData<C, M>
153where
154    C: Clone + Eq + 'static,
155    M: PartialEq + Clone + TableDataModel<C> + 'static,
156    M::Key: Hash,
157{
158    type Iterator<'i> = StateModelIter<'i, M::Key, M::Item, C>;
159    type Item = M::Item;
160    type Key = M::Key;
161
162    fn len(&self) -> usize {
163        self.model.len()
164    }
165
166    fn is_empty(&self) -> bool {
167        self.model.is_empty()
168    }
169
170    fn iter(&self) -> Self::Iterator<'_> {
171        self.model.iter()
172    }
173}
174
175impl<C, M> Clone for UseTableData<C, M>
176where
177    C: Clone + Eq + 'static,
178    M: PartialEq + Clone + TableDataModel<C> + 'static,
179    M::Key: Hash,
180{
181    fn clone(&self) -> Self {
182        Self {
183            model: self.model.clone(),
184        }
185    }
186}
187
188impl<C, M> PartialEq for UseTableData<C, M>
189where
190    C: Clone + Eq + 'static,
191    M: PartialEq + Clone + TableDataModel<C> + 'static,
192    M::Key: Hash,
193{
194    fn eq(&self, other: &Self) -> bool {
195        self.model == other.model
196    }
197}
198
199#[cfg(test)]
200mod test {
201    use super::*;
202    use crate::prelude::{
203        Cell, CellContext, OnToggleCallback, TableEntryRenderer, TableModelEntry,
204    };
205
206    #[test]
207    fn test_eq() {
208        #[derive(Clone)]
209        struct MockModel {}
210
211        impl TableEntryRenderer<()> for String {
212            fn render_cell(&self, _context: CellContext<'_, ()>) -> Cell {
213                html!().into()
214            }
215        }
216
217        impl TableModel<()> for MockModel {
218            type Iterator<'i>  = std::vec::IntoIter<TableModelEntry<'i, Self::Item, Self::Key, ()>> where
219            Self: 'i;
220
221            type Item = String;
222            type Key = usize;
223
224            fn len(&self) -> usize {
225                0
226            }
227
228            fn iter(&self) -> Self::Iterator<'_> {
229                Vec::new().into_iter()
230            }
231        }
232
233        let a = OnToggleCallback::<(), MockModel>::default();
234        let b = OnToggleCallback::<(), MockModel>::default();
235        let c = a.clone();
236
237        assert_eq!(a, c);
238        assert_eq!(c, a);
239
240        assert_ne!(a, b);
241        assert_ne!(b, c);
242    }
243}