patternfly_yew/components/table/model/
hook.rs1use 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 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}