yew_infinite_for/
lib.rs

1use web_sys::HtmlDivElement;
2use yew::prelude::*;
3
4static mut RENDER_LIST: Vec<Html> = Vec::new();
5
6#[derive(Debug, Clone, PartialEq, Properties)]
7pub struct Props {
8    /// This callback accept a Two-tuple:
9    /// - **n: usize:** the serial number of the item
10    /// - **ret: Callback<Html>:** Html setter of the item
11    pub request: Option<Callback<(usize, Callback<Html>)>>,
12    pub children: Option<Children>,
13    pub is_direction_row: Option<bool>,
14}
15
16/// A list component that scrolls infinitely for Yew.
17///
18/// # Style to Overflow
19///
20/// You have to be sure `InfiniteFor` can be overflow.
21/// Generally this is not a problem in other components.
22/// If you plan to put `InfiniteFor` directly in the
23/// `body`, you can use a style like this:
24///
25/// ```ignore
26/// html! {
27///     <div
28///         // You have to be sure `InfiniteFor` can be overflow.
29///         // Generally this is not a problem in other components.
30///         // If you plan to put `InfiniteFor` directly in the
31///         // `body`, you can use a style like this:
32///         style="\
33///             height: 99vh;\
34///             width: 100wh;\
35///         "
36///     >
37///         <InfiniteFor/>
38///     </div>
39/// }
40/// ```
41///
42/// # Example
43///
44/// ```ignore
45/// #[function_component(App)]
46/// fn app() -> Html {
47///     let request = Callback::from(|(n, ret): (usize, Callback<Html>)| {
48///         ret.emit(html!(
49///             <h1>
50///                 {format!("This is tag No.{n}")}
51///             </h1>
52///         ))
53///     });
54///
55///     html! {
56///         <InfiniteFor
57///             // use this attribute to switch mode from column to row
58///             is_direction_row={true}
59///             // use this callback to offer Html of the items
60///             {request}
61///         >
62///             // children is the load sign
63///             // at the bottom of the page
64///             <h4>
65///                 {"The end of page!"}
66///             </h4>
67///         </InfiniteFor>
68///     }
69/// }
70/// ```
71#[function_component(InfiniteFor)]
72pub fn infinite_for(props: &Props) -> Html {
73    let request = props
74        .request
75        .clone()
76        .unwrap_or(Callback::from(|i: (usize, Callback<Html>)| {
77            i.1.emit(html!(
78                <div
79                    style="\
80                        min-height: 300px;\
81                        min-width: 700px;\
82                        height: 300px;\
83                        width: 700px;\
84                        border-radius: 5px;\
85                        background-color: rgba(222, 240, 255, 0.262);\
86                        margin: 5px;\
87                    "
88                >
89                    <h1>{i.0}</h1>
90                </div>
91            ))
92        }));
93
94    let loading_block = props.children.clone();
95    let request_number_state = use_state(|| 0usize);
96    let request_times_state = use_state(|| 0usize);
97    let list_state = use_state(|| html!(<></>));
98    let is_bottom_state = use_state(|| true);
99
100    let is_direction_row = props.is_direction_row;
101    let list_ref = NodeRef::default();
102    let loading_block_ref = NodeRef::default();
103
104    let list_push = {
105        Callback::from(move |item: Html| unsafe {
106            RENDER_LIST.push(item);
107        })
108    };
109
110    {
111        let list_ref = list_ref.clone();
112        let request = request.clone();
113        let list_push = list_push.clone();
114        let list_state = list_state.clone();
115        let request_number_state = request_number_state.clone();
116        let request_times_state = request_times_state.clone();
117
118        use_effect(move || {
119            let list_ref = list_ref.cast::<HtmlDivElement>().unwrap();
120
121            if list_ref.scroll_height() - 100 < list_ref.client_height()
122                && list_ref.scroll_width() - 100 < list_ref.client_width()
123            {
124                request_number_state.set(*request_number_state + 1);
125                request_times_state.set(*request_times_state + 1);
126
127                let key = *request_times_state;
128
129                request.emit((key, list_push));
130                unsafe {
131                    list_state.set(
132                        RENDER_LIST
133                            .clone()
134                            .into_iter()
135                            .enumerate()
136                            .map(|(key, i)| html!(<div {key}>{i}</div>))
137                            .collect(),
138                    )
139                }
140            }
141            || ()
142        })
143    };
144
145    let onscroll = {
146        let list_ref = list_ref.clone();
147        let loading_block_ref = loading_block_ref.clone();
148        let is_bottom_state = is_bottom_state.clone();
149
150        let list_push = list_push.clone();
151        let request = request.clone();
152        let list_state = list_state.clone();
153        let request_number_state = request_number_state.clone();
154        let request_times_state = request_times_state.clone();
155
156        Callback::from(move |_| {
157            let list_ref = list_ref.cast::<HtmlDivElement>().unwrap();
158            let list_pos = (list_ref.scroll_left(), list_ref.scroll_top());
159
160            let refresh_range = loading_block_ref.cast::<HtmlDivElement>().unwrap();
161
162            let refresh_range = if is_direction_row.unwrap_or(false) {
163                refresh_range.scroll_width()
164            } else {
165                refresh_range.scroll_height()
166            };
167
168            let befor_is_bottom_state = *is_bottom_state;
169
170            let after_is_bottom_state = match (
171                is_direction_row,
172                list_ref.scroll_width() - list_pos.0 - list_ref.client_width() <= refresh_range,
173                list_ref.scroll_height() - list_pos.1 - list_ref.client_height() <= refresh_range,
174            ) {
175                (Some(true), true, _) | (None, _, true) | (Some(false), _, true) => true,
176                _ => false,
177            };
178
179            if !befor_is_bottom_state && after_is_bottom_state {
180                request_times_state.set(*request_times_state + *request_number_state);
181
182                for i in 0..*request_number_state {
183                    let key = *request_times_state + i;
184
185                    request.emit((key, list_push.clone()));
186                    unsafe {
187                        list_state.set(
188                            RENDER_LIST
189                                .clone()
190                                .into_iter()
191                                .enumerate()
192                                .map(|(key, i)| html!(<div {key}>{i}</div>))
193                                .collect(),
194                        )
195                    }
196                }
197            }
198
199            list_ref.scroll_to_with_x_and_y(list_pos.0 as f64, list_pos.1 as f64);
200
201            is_bottom_state.set(after_is_bottom_state);
202        })
203    };
204
205    html! {
206        <>
207            <div
208                ref={list_ref}
209                {onscroll}
210                style={
211                    format!("\
212                        height: 100%;\
213                        width: 100%;\
214                        margin-bottom: 5px;\
215                        display: flex;\
216                        justify-content: flex-start;\
217                        align-items: center;\
218                        flex-direction: {};\
219                        overflow: auto;\
220                    ",
221                    if is_direction_row == Some(true) {"row"}
222                    else {"column"})
223                }
224            >
225                //======List=======
226                {(*list_state).clone()}
227                //==Loading Block==
228                <div
229                    ref={loading_block_ref}
230                    style="\
231                        width: auto;\
232                        height: auto;\
233                    "
234                >
235                    {
236                        if let Some(loading_block) = loading_block {
237                            loading_block
238                        } else {
239                            Children::new(vec![html!(
240                                <h4
241                                    style="\
242                                        background: rgb(18,23,46);\
243                                        padding: 5px;\
244                                        border-radius: 5px;\
245                                    "
246                                >
247                                    {"到底了"}
248                                </h4>
249                            )])
250                        }
251                    }
252                </div>
253            </div>
254        </>
255    }
256}